Метод dict.get() возвращает указатель
Скажем, у меня есть этот код:
my_dict = {}
default_value = {'surname': '', 'age': 0}
# get info about john, or a default dict
item = my_dict.get('john', default_value)
# edit the data
item[surname] = 'smith'
item[age] = 68
my_dict['john'] = item
Проблема становится ясной, если мы теперь проверим значение default_value:
>>> default_value
{'age': 68, 'surname': 'smith'}
Очевидно, что my_dict.get()
не вернул значение по умолчанию_value, но указатель (?) на него.
Проблема может быть решена путем изменения кода на:
item = my_dict.get('john', {'surname': '', 'age': 0})
но это не похоже на хороший способ сделать это. Любые идеи, комментарии?
Ответы
Ответ 1
item = my_dict.get('john', default_value.copy())
Вы всегда передаете ссылку в Python.
Это не имеет значения для неизменяемых объектов, таких как str
, int
, tuple
и т.д., так как вы не можете их изменить, укажите только имя на другом объекте, но оно выполняется для изменяемых объектов, таких как list
, set
и dict
. Вам нужно привыкнуть к этому и всегда помнить об этом.
Изменить: Зак Блум и Джонатан Штернберг указывают методы, которые вы можете использовать, чтобы избежать вызова copy
при каждом поиске. Вы должны использовать либо метод defaultdict
, что-то вроде первого метода Джонатана, либо:
def my_dict_get(key):
try:
item = my_dict[key]
except KeyError:
item = default_value.copy()
Это будет быстрее, чем if
, когда ключ почти всегда уже существует в my_dict
, , если dict
большой. Вам не нужно обертывать его функцией, но вы, вероятно, не хотите, чтобы эти четыре строки при каждом доступе к my_dict
.
См. ответ Джонатана для таймингов с небольшим dict
. Метод get
работает на всех уровнях, которые я тестировал, но метод try
лучше работает при больших размерах.
Ответ 2
Не используйте get. Вы можете сделать:
item = my_dict.get('john', default_value.copy())
Но для этого требуется, чтобы словарь был скопирован, даже если запись словаря существует. Вместо этого рассмотрите возможность проверки наличия значения.
item = my_dict['john'] if 'john' in my_dict else default_value.copy()
Единственная проблема заключается в том, что он будет выполнять два поиска для "john" вместо одного. Если вы хотите использовать дополнительную строку (и None - это не возможное значение, которое вы можете получить из словаря), вы можете сделать:
item = my_dict.get('john')
if item is None:
item = default_value.copy()
EDIT: Я думал, что сделаю некоторые сравнения скорости с тайм-аутом. Значение default_value и my_dict были глобальными. Я сделал их каждый для обоих, если ключ был там, и если была промашка.
Использование исключений:
def my_dict_get():
try:
item = my_dict['key']
except KeyError:
item = default_value.copy()
# key present: 0.4179
# key absent: 3.3799
Использование get и проверка, если оно отсутствует.
def my_dict_get():
item = my_dict.get('key')
if item is None:
item = default_value.copy()
# key present: 0.57189
# key absent: 0.96691
Проверка его существования с помощью специального if/else синтаксиса
def my_dict_get():
item = my_dict['key'] if 'key' in my_dict else default_value.copy()
# key present: 0.39721
# key absent: 0.43474
Наивное копирование словаря.
def my_dict_get():
item = my_dict.get('key', default_value.copy())
# key present: 0.52303 (this may be lower than it should be as the dictionary I used was one element)
# key absent: 0.66045
По большей части все, кроме тех, которые используют исключения, очень похожи. По какой-то причине особый if/else синтаксис имеет наименьшее время (не знаю почему).
Ответ 3
В Python dicts - оба объекта (поэтому они всегда передаются как ссылки) и изменяемы (что означает, что они могут быть изменены без воссоздания).
Вы можете копировать словарь каждый раз, когда вы его используете:
my_dict.get('john', default_value.copy())
Вы также можете использовать коллекцию defaultdict:
from collections import defaultdict
def factory():
return {'surname': '', 'age': 0}
my_dict = defaultdict(factory)
my_dict['john']
Ответ 4
Главное, чтобы понять, что все в Python является передачей по ссылке. Имя переменной в языке C-стиля обычно является сокращением для объектной области памяти, а присвоение этой переменной делает копию другой объектной области... в Python переменные являются просто ключами в словаре (locals()
), и акт назначения просто сохраняет новую ссылку. (Технически, все является указателем, но это деталь реализации).
Это имеет ряд последствий: основной из них никогда не будет неявной копией объекта, потому что вы передали его функции, назначили ей и т.д. Единственный способ получить копию - это явно сделать это, Python stdlib предлагает copy
модуль, который содержит некоторые вещи, включая функцию copy()
и deepcopy()
, если вы хотите явно сделать копия чего-то. Кроме того, некоторые типы предоставляют собственную функцию .copy()
, но это не стандарт или последовательно реализуется. Другие, которые неизменны, обычно предлагают метод .replace()
, который делает мутированную копию.
В случае вашего кода передача в исходном экземпляре явно не работает, и сделать копию заблаговременно (когда вам это может понадобиться) является расточительным. Итак, самое простое решение, вероятно,...
item = my_dict.get('john')
if item is None:
item = default_dict.copy()
Было бы полезно в этом случае, если .get()
поддерживал передачу в функции конструктора значения по умолчанию, но, возможно, перепроектировал базовый класс для случая границы.
Ответ 5
потому что my_dict.get('john', default_value.copy())
создаст копию default dict каждый раз, когда вызывается get (даже когда "john" присутствует и возвращается), это быстрее и очень хорошо использовать этот try/except вариант:
try:
return my_dict['john']
except KeyError:
return {'surname': '', 'age': 0}
В качестве альтернативы вы также можете использовать defaultdict
:
import collections
def default_factory():
return {'surname': '', 'age': 0}
my_dict = collections.defaultdict(default_factory)