Может ли простая разница в именах переменных Python3 изменить способ выполнения кода?

Этот код...

class Person:
    num_of_people = 0

    def __init__(self, name):
        self.name = name
        Person.num_of_people += 1

    def __del__(self):
        Person.num_of_people -= 1

    def __str__(self):
        return 'Hello, my name is ' + self.name

cb = Person('Corey')
kb = Person('Katie')
v = Person('Val')

Выдает следующую ошибку...

Exception AttributeError: "'NoneType' object has no attribute 'num_of_people'" in <bound method Person.__del__ of <__main__.Person object at 0x7f5593632590>> ignored

Но этого кода нет.

class Person:
    num_of_people = 0

    def __init__(self, name):
        self.name = name
        Person.num_of_people += 1

    def __del__(self):
        Person.num_of_people -= 1

    def __str__(self):
        return 'Hello, my name is ' + self.name

cb = Person('Corey')
kb = Person('Katie')
vb = Person('Val')

Единственная разница, которую я вижу, это последнее имя переменной "vb" и "v".

Я склоняюсь к Python и сейчас работаю над ООП.


person Corey    schedule 04.04.2014    source источник
comment
@StevenRumbalski: Короче говоря, да. Но только при выходе интерпретатора.   -  person Martijn Pieters    schedule 04.04.2014
comment
Первый код не создает это исключение. Покажите полную трассировку. (Исправление: в Python 3.3 или более поздней версии это исключение не создается. В версии 3.2 оно возникает.)   -  person Wooble    schedule 04.04.2014
comment
@Вубл Неее! Вот чего мне не хватало..   -  person aIKid    schedule 04.04.2014
comment
@Wooble: Это потому, что хеширование словаря для строк рандомизировано в 3.3. Это произойдет и в 3.3, когда звезды выстроятся как раз так, чтобы ключи столкнулись в правильном порядке. Другими словами, перезапустите тест несколько раз, и вы увидите, что это происходит при некоторых запусках.   -  person Martijn Pieters    schedule 04.04.2014
comment
@Wooble: И последнее, но не менее важное: вы точно не увидите эту ошибку в CPython 3.4, поскольку она имеет новый безопасный код завершения объекта, который полностью устраняет причину этой ошибки.   -  person Martijn Pieters    schedule 04.04.2014
comment
Примечание. Я бы не сказал, что то, что вы видите, является ошибкой. Это игнорируемое исключение, а также правильно игнорируемое исключение, поэтому я бы сказал, что это скорее предупреждение, чем ошибка. Если бы вы не связывались с __del__, у вас не было бы этой проблемы, и я считаю, что код, который вы написали, неправильно использует __del__. На самом деле в python‹3.4 вы можете легко создать несколько циклов ссылок, чтобы ваш счетчик num_of_people был выключен.   -  person Bakuriu    schedule 04.04.2014
comment
Не только Python3. В Python 2.7.3 я получаю две такие ошибки для первого примера и одну для второго.   -  person Evgeni Sergeev    schedule 05.04.2014


Ответы (1)


Да, хотя это вызвано не столько именем переменной, не напрямую.

Когда Python завершает работу, все модули также удаляются. Способ очистки модулей заключается в установке для всех глобальных переменных в модуле значения None (чтобы эти ссылки больше не относились к исходным объектам). Эти глобальные переменные являются ключами в объекте словаря, и, поскольку словари упорядочены произвольно, переименование одной переменной может изменить порядок, в котором переменные очищаются.

Когда вы переименовали v в vb, вы изменили порядок, в котором очищаются переменные, и теперь Person очищается последним.

Одним из обходных путей является использование type(self).num_of_people -= 1 в методе __del__ вместо этого:

def __del__(self):
    type(self).num_of_people -= 1

потому что экземпляр всегда будет иметь ссылку на класс или проверить, не установлено ли Person значение None:

def __del__(self):
    if Person is not None:
        Person.num_of_people -= 1

Два примечания:

  • CPython 3.4 больше не устанавливает для глобальных переменных значение None (в большинстве случаев) согласно Безопасная финализация объекта; см. PEP 442.

  • CPython 3.3 автоматически применяет рандомизированную соль хеша к используемым str ключам. в globals словаре; это делает поведение, которое вы наблюдали, еще более случайным, просто повторный запуск вашего кода несколько раз может вызвать или не вызвать сообщение об ошибке.

person Martijn Pieters    schedule 04.04.2014
comment
Тьфу.. Ответ блестящий, но я не очень понимаю это. Что вы подразумеваете под «при выходе из интерпретатора»? Не могли бы вы объяснить? - person aIKid; 04.04.2014
comment
@aIKid: когда Python завершает работу (или если вы удаляете модуль, удаляя все ссылки на него и удаляя его из sys.modules, но это не часто делается), вызывается модуль __del__, который затем продолжается, сначала очищая все глобальные переменные в модуле. перепривязав их имена к None. - person Martijn Pieters; 04.04.2014
comment
Таким образом, исключение возникает только тогда, когда мы выходим из интерпретатора.. ‹- Это неверно, верно? - person aIKid; 04.04.2014
comment
@aIKid: это должно происходить только при выходе из интерпретатора или при явном удалении модуля. - person Martijn Pieters; 04.04.2014
comment
Вот это да. Я не знаю, есть такое! Ну, что ж, спасибо! - person aIKid; 04.04.2014
comment
Именно за такие вещи я люблю StackOverflow. +1, очень интересно. - person asteri; 04.04.2014
comment
отличный технический ответ, не могли бы вы обобщить его для меня на более высоком уровне? - person Corey; 04.04.2014
comment
@Corey: здесь нет более высокого уровня; Суть в том, что во время финализации глобальным переменным, таким как Person, v и vb, назначается None, поэтому вы не можете рассчитывать на то, что Person останется классом в __del__. - person Martijn Pieters; 04.04.2014
comment
@sds: финализация привередлива; установив для глобальных переменных значение None, многие общие циклические ссылки будут корректно разорваны без вызова (медленного и совершенно ненужного) изменения размеров словарей globals. Наконец-то нашли лучший способ для Python 3.4, так что все это больше не проблема с этой версией. - person Martijn Pieters; 04.04.2014