Метод 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()
не возвращал значение default_value, но указатель (?) на него.
Проблему можно обойти, изменив код на:
item = my_dict.get('john', {'surname': '', 'age': 0})
но это не очень хороший способ сделать это. Есть идеи, комментарии?
5 ответов
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
метод лучше при больших размерах.
Не используйте get. Вы могли бы сделать:
item = my_dict.get('john', default_value.copy())
Но для этого необходимо скопировать словарь, даже если запись словаря существует. Вместо этого рассмотрите возможность проверки наличия значения.
item = my_dict['john'] if 'john' in my_dict else default_value.copy()
Единственная проблема с этим состоит в том, что он выполнит два поиска для "Джона" вместо одного. Если вы хотите использовать дополнительную строку (а значение "Нет" не является возможным значением из словаря), вы можете сделать следующее:
item = my_dict.get('john')
if item is None:
item = default_value.copy()
РЕДАКТИРОВАТЬ: Я думал, что я сделаю некоторые сравнения скорости с timeit. 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 и проверка, если это None.
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 имеет наименьшее время по какой-то причине (понятия не имею, почему).
В 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']
Главное, что нужно понять, это то, что все в Python передается по ссылке. Имя переменной в языке C-стиля обычно является сокращением для области памяти в форме объекта, и присвоение этой переменной делает копию другой области в форме объекта... в Python переменные - это просто ключи в словаре (locals()
), а акт о назначении просто хранит новую ссылку. (Технически, все это указатель, но это деталь реализации).
Это имеет ряд последствий, главное из которых состоит в том, что никогда не будет неявной копии объекта, созданного, потому что вы передали его функции, присвоили ее и т. Д. Единственный способ получить копию - это явно сделать это. Питон stdlib предлагает copy
модуль, который содержит некоторые вещи, в том числе copy()
а также deepcopy()
функция, когда вы хотите явно сделать копию чего-либо. Кроме того, некоторые типы выставляют .copy()
функции свои, но это не стандарт или последовательно реализуется. Другие, которые являются неизменными, как правило, предлагают .replace()
метод, который делает мутированную копию.
В случае вашего кода передача в исходном экземпляре, очевидно, не работает, а создание копии заранее (когда вам это может не понадобиться) расточительно. Так что самое простое решение, вероятно,...
item = my_dict.get('john')
if item is None:
item = default_dict.copy()
Было бы полезно в этом случае, если .get()
поддерживается передача в функцию конструктора значений по умолчанию, но это, вероятно, слишком сложный базовый класс для пограничного случая.
Так как my_dict.get('john', default_value.copy())
будет создавать копию по умолчанию dict каждый раз, когда вызывается get (даже когда присутствует и возвращается 'john'), это быстрее и очень хорошо использовать эту опцию try/ исключением:
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)