Python3变量名称的简单差异是否能改变代码运行方式?

51

这段代码...

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,并且现在正在处理面向对象编程方面的内容。


2
@StevenRumbalski:简而言之,是的。但仅在解释器退出时。 - Martijn Pieters
6
第一段代码不会产生该异常。请展示完整的追踪信息。(更正:在Python 3.3或更高版本中不会产生该异常,在3.2版本中会产生。) - Wooble
@Wooble 不是的!那就是我缺少的东西。。 - aIKid
3
这是因为在Python 3.3中,字符串的字典哈希是随机的。这种情况也会在3.3中发生,当关键字以正确的顺序碰撞时,就会出现这种情况。换句话说,重新运行测试多次,你会看到它在某些运行中发生。请注意,这里强调的是“某些”,并非每次运行都会出现这种情况。 - Martijn Pieters
4
最后但并非最不重要的,你肯定不会在CPython 3.4中看到这个错误了,因为它有一个新的安全对象终结代码路径,可以完全消除这个错误的根源。 - Martijn Pieters
显示剩余2条评论
1个回答

59

是的,尽管不是变量名直接导致这种情况。

当Python退出时,所有模块也都被删除了。模块清理的方式是将模块中的所有全局变量设置为None(因此这些引用不再引用原始对象)。这些全局变量是字典对象中的键,由于字典是任意排序的,重命名一个变量可以改变变量被清除的顺序。

当您将v重命名为vb时,您改变了变量被清除的顺序,现在Person最后被清除。

一种解决方法是在__del__方法中使用type(self).num_of_people -= 1

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自动对globals字典中使用的str键应用随机哈希盐;这使得您观察到的行为更加随机,只需多次运行您的代码即可触发或未触发错误消息。


2
唉...答案很棒,但我真的不太理解这个。你说的“在解释器退出时”是什么意思?能否请您解释一下? - aIKid
3
当Python退出时,或者如果您通过删除对它的所有引用并从sys.modules中删除它来删除模块(但这种情况不经常发生),则会调用模块__del__,然后通过将其名称重新绑定为None来首先清除模块中的所有全局变量。 - Martijn Pieters
1
@aIKid:只有在退出解释器或显式删除模块时,才应出现这种情况。 - Martijn Pieters
2
@Corey: 这里并没有更高的级别; 底线是在 finalization 期间,像 Personvvb 这样的全局变量都被赋值为 None,因此你无法保证在 __del__Person 仍然是类。 - Martijn Pieters
3
@sds:最终化过程很棘手;通过将全局变量设置为None,可以有效地打破许多常见的循环引用,而不需要调整globals字典(这是缓慢且完全不必要的)。Python 3.4中finally找到了更好的解决方法,因此在该版本中,整个问题已经不再是一个问题。 - Martijn Pieters
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接