Присвоение значений несуществующим атрибутам объектов

Я делаю домашнее задание по интеллектуальному анализу данных с помощью python (2.7). Я создал словарь веса для всех слов (которые существуют в категории) и для слов, которых нет в этом слове, я хочу назначить значение по умолчанию. Сначала я попробовал использовать setdefault для каждого ключа перед его использованием, он работает отлично, но почему-то мне кажется, что он не выглядит таким питоническим. Поэтому я попытался использовать defaultdict, который большую часть времени работает нормально. Однако иногда он возвращает неверное значение. Сначала я подумал, что это может быть вызвано defaultdict или лямбда-функцией, но, видимо, ошибок нет.

for node in globalTreeRoot.traverse():
    ...irrelevant...
    weight_dict = {.......}
    default_value = 1.0 / (totalwords + dictlen)
    node.default_value = 1.0/ (totalwords + dictlen)
    ......
    node.weight_dict_ori = weight_dict
    node.weight_dict = defaultdict(lambda :default_value,weight_dict)

Итак, когда я пытался напечатать значение, которого не существует во время цикла, оно дает мне правильное значение. Однако после завершения выполнения кода, когда я пытаюсь:

print node.weight_dict["doesnotexist"],

он дает мне неправильное значение, а когда оно неверно, обычно значение, относящееся к другому узлу. Я попытался выполнить поиск в системе именования Python или динамически присвоить значения атрибутам объекта, но не понял.

Кстати, defaultdict быстрее, чем использование setdefault (k, v) каждый раз?


person tartaruga_casco_mole    schedule 22.06.2015    source источник


Ответы (3)


Это не вариант использования defaultdict.

Вместо этого просто используйте get для получения значений из словаря.

val = dict.get("doesnotexist", 1234321)

вполне приемлемо python "get" имеет второй параметр, значение по умолчанию, если ключ не был найден.

Если вам это нужно только для "get", defaultdict будет немного излишним. Он предназначен для использования следующим образом:

example = defaultdict(list)
example[key].append(1)

без необходимости явно инициализировать комбинацию списка ключей каждый раз. Для числовых значений улучшения незначительны:

ex1, ex2 = dict, defaultdict(lambda: 0)
ex1[key] = ex1.get(key, 0) + 1
ex2[key] += 1

Ваша первоначальная проблема, вероятно, связана с тем, что вы повторно использовали переменную, хранящую вес. Убедитесь, что он локальный для цикла!

var = 1
ex3 = defaultdict(lambda: var)
var = 2
print ex3[123]

должен возвращать текущее значение var=2. Он не подставляется в словарь при инициализации, но ведет себя так, как если бы вы определили функцию в этой позиции, обращаясь к «внешней» переменной var.

Взлом такой:

def constfunc(x):
  return lambda: x
ex3 = defaultdict(constfunc(var))

Теперь constfunc вычисляется при инициализации, x - это локальная переменная вызова, а лямбда теперь возвращает x, который больше не меняется. Я думаю, вы можете встроить это (непроверено):

ex3 = defaultdict((lambda x: lambda: x)(var))

Взгляните на магию Python, фиксирующую «замыкания» и аномалии императивных языков, претендующих на функциональное программирование.

person Has QUIT--Anony-Mousse    schedule 22.06.2015
comment
Чтобы быть явным для OP, форма с двумя аргументами get позволяет указать значение по умолчанию, которое возвращается, если указанный ключ не существует в dict. - person Tom Morris; 22.06.2015
comment
Спасибо! Я думаю, что get (key, defaultvalue) - это именно то, что я искал. А также спасибо за объяснение по поводу закрытия. Я думаю, что неправильно понял его метод определения переменных. - person tartaruga_casco_mole; 23.06.2015

setdefault - это определенно то, что вы должны использовать для установки значения по умолчанию.

for node in globalTreeRoot.traverse():
    node.default_value = 1.0 / (totalwords + dictlen)
    node.weight_dict = {}
    # if you did want to use a defaultdict here for some reason, it would be
    #   node.weight_dict = defaultdict(lambda: node.default_value)
    for word in wordlist:
        value = node.weight_dict.setdefault(word, node.default_value)
person Adam Smith    schedule 22.06.2015
comment
На самом деле это defaultdict (lambda: default_value, weight_dict). Вы должны дать ему диктат. Я знаю, что это работает, но пытаюсь разобраться, что здесь не так. - person tartaruga_casco_mole; 22.06.2015
comment
@RafaelJ, если вы дадите ему dict, он инициализирует этот dict как часть defaultdict. Непонятно, зачем вам это нужно. d1 = {'key':'value'}; d2 = defaultdict(lambda: 42, d1), после чего d2 == {'key':'value'} и d2['new_key']; d2 = {'key':'value', 'new_key':42}. - person Adam Smith; 22.06.2015

Видимо, с defaultdict что-то не так.

d1 = {"a":10,"b":9,"c":8}
seven = 7
d2 = defaultdict(lambda :seven,d1)
seven = 8
d3 = defaultdict(lambda :seven,d1)

И результат:

>>> d2[4234]
8

Я до сих пор не понимаю, почему это так работает. Что касается работы, я буду придерживаться setdefault.

ОБНОВЛЕНИЕ: Спасибо за ответ. Я неправильно понял, как работает область видимости переменных в Python.

person tartaruga_casco_mole    schedule 22.06.2015
comment
Не устанавливайте seven=8. Неправильно ведет себя не defaultdict, а неинтуитивное определение области видимости переменных в Python в сочетании с несоответствующим повторным использованием переменных. - person Has QUIT--Anony-Mousse; 22.06.2015
comment
Не так уж и сложно изучить область видимости переменной. lambda: seven - это функция, которая возвращает текущее значение переменной seven. Если бы вы хотели его локальную область видимости, у вас был бы lambda s=seven: s, который работал бы правильно. - person Adam Smith; 22.06.2015
comment
@AdamSmith, это уродливый взлом, чтобы сделать его локальным, ИМХО. И поведение, которое меня тоже очень часто задело, когда я хотел, чтобы значения по умолчанию были установлены для переменной ... Область видимости переменных - это одна из вещей, которые я ненавижу в python ... - person Has QUIT--Anony-Mousse; 22.06.2015
comment
@ Anony-Mousse согласилась, что это уродливый хакер, но я думаю, нам придется согласиться, чтобы не соглашаться по поводу выбора области для переменной. Не знаю, зачем foo = 3; bar = lambda: foo; foo = 4; bar() давать 3! - person Adam Smith; 22.06.2015
comment
Я не говорю, что альтернатива лучше. Но он должен быть явным и не должен требовать такого взлома для получения другого поведения. - person Has QUIT--Anony-Mousse; 23.06.2015