Метод dict.get () возвращает указатель - PullRequest
15 голосов
/ 23 августа 2011

Допустим, у меня есть этот код:

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 ]

22 голосов
/ 23 августа 2011
item = my_dict.get('john', default_value.copy())

Вы всегда передаваете ссылку в Python.

Это не имеет значения для неизменяемых объектов, таких как str, int, tuple и т. Д.Поскольку вы не можете их изменить, указывайте только имя на другом объекте, но это относится к изменяемым объектам, таким как list, set и dict.Вы должны привыкнуть к этому и всегда помнить об этом.

Edit: Zach Bloom и Jonathan Sternberg оба указывают на методы, которые вы можете использовать, чтобы избежать вызова 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 лучше при больших размерах.

9 голосов
/ 23 августа 2011

Не используйте get. Вы могли бы сделать:

item = my_dict.get('john', default_value.copy())

Но для этого необходимо скопировать словарь , даже если запись словаря существует . Вместо этого попробуйте просто проверить, есть ли значение.

item = my_dict['john'] if 'john' in my_dict else default_value.copy()

Единственная проблема с этим состоит в том, что он выполнит два поиска для 'john' вместо одного. Если вы хотите использовать дополнительную строку (а значение «Нет» - это значение, которое вы не можете получить из словаря), вы можете сделать следующее:

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 имеет наименьшее время по какой-то причине (не знаю почему).

8 голосов
/ 23 августа 2011

В 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']
3 голосов
/ 23 августа 2011

Главное, что нужно понять, это то, что все в 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() поддерживал передачу в функцию конструктора значений по умолчанию, но это, вероятно, перебор базового класса для пограничного случая.

2 голосов
/ 30 сентября 2011

, поскольку my_dict.get('john', default_value.copy()) будет создавать копию по умолчанию dict каждый раз, когда вызывается get (даже когда присутствует и возвращается 'john'), использовать эту опцию try / exc быстрее и очень хорошо :

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)
...