Python 静态变量解析
我正在尝试为某个特定的类创建一个计数器,用来统计这个类创建的所有实例数量。很明显,我的代码没有让 self.__class__.counter
增加到超过1。我到底哪里出错了呢?
class Feature(object):
counter = 0
def __init__(self, line):
self.id = self.__class__.counter
self.__class__.counter = self.__class__.counter + 1
def __repr__(self):
return "Feature: id=%d name=%s" % (self.id,self.name,)
更新: 其实这段代码是按预期工作的。聪明的人们留下了一些非常详细的解释,讲述了在Python中如何处理静态变量。我投票关闭了我的问题,但我不希望这些答案消失。
4 个回答
为了更好地说明 self.__class__.attribute
和 ClassName.attribute
之间的区别:
class Feature(object):
counter = 0
def __init__(self):
self.id = self.__class__.counter
self.__class__.counter += 1
class Sub(Feature):
def __init__(self):
super(Sub, self).__init__()
print [(vars(Feature()), vars(Sub())) for i in xrange(3)]
print Feature.counter, Sub.counter, id(Feature.counter) == id(Sub.counter)
class Feature(object):
counter = 0
def __init__(self):
self.id = Feature.counter
Feature.counter += 1
class Sub(Feature):
def __init__(self):
super(Sub, self).__init__()
print [(vars(Feature()), vars(Sub())) for i in xrange(3)]
print Feature.counter, Sub.counter, id(Feature.counter) == id(Sub.counter)
>>> [({'id': 0}, {'id': 1}), ({'id': 1}, {'id': 2}), ({'id': 2}, {'id': 3})]
3 4 False
>>> [({'id': 0}, {'id': 1}), ({'id': 2}, {'id': 3}), ({'id': 4}, {'id': 5})]
6 6 True
在计算实例的时候,我更喜欢这种方式:
from itertools import count
class MyClass():
_getId = count(0).next
def __init__(self):
self.id = self._getId()
这是我会做的(虽然你的代码对我来说运行得很好,所以我怀疑你观察到的问题可能不是因为代码本身)。
class Feature(object):
counter = 0
def __init__(self):
self.id = Feature.counter
Feature.counter += 1
如果你愿意,可以把 Feature
替换成 type(self)
(或者 self.__class__
,效果是一样的),但要注意它们在有子类的情况下表现会有所不同(这可能就是你的问题所在)。
在Python中,使用类和实例变量的规则非常简单,记住这些规则会很有帮助:
- 当你用
self.name
读取一个属性的值时,如果self
里没有直接包含名为name
的属性,它会先查找self
的类,然后再查找基类的层级。 - 当你用
self.name = ...
写入一个属性的值时,总是会直接在self
中绑定name
。
特别是,当你写入一个值时,之前读取同一个属性的地方并不重要。
所以如果在你的实际程序中,你实例化了 Feature 的一个 subclass
,我称它为 Sub
,那么这一行:
self.__class__.counter = self.__class__.counter + 1
可能不会如你所预期的那样工作。它会在第一次创建 Sub
的实例时从 Feature.counter
读取值,但会写入到 Sub.counter
。之后的读取会去 Sub.counter
(因为它现在存在了),所以 Sub
的实例会得到递增的ID,但它们并没有增加 Feature.counter
的值,因此其他 Feature
的子类实例可能会得到重复的ID。
所以如果我把 counter
看作是一个在 Feature
命名空间中的全局变量,并且我希望所有 Feature
的子类共享同一个计数器,我会使用 Feature.counter
。如果我希望每一个 Feature
的子类都有自己的独立计数器,我会使用 type(self).counter
(但那样的话你需要做一些事情来初始化它们)。