Python中的属性“__class__”究竟是什么?

34 投票
4 回答
33270 浏览
提问于 2025-04-17 05:55

我有一个关于 __class__ 在 Python 中的问题。

文档上说 __class__ 是一个类实例所属的类。于是我做了一系列实验:

class counter:
    count = 0
    def __init__(self):
            self.__class__.count += 1

NewCounter1 = counter()
print NewCounter1.count   #The result is 1
NewCounter2 = counter()
print NewCounter2.count   #The result is 2
print NewCounter2.__class__.count is NewCounter2.count  #result: True

一切都很顺利。

然后我输入了如下代码:

NewCounter2.__class__.count = 3

print NewCounter1.count                    #result:3
print NewCounter1.__class__.count      #result:3
print NewCounter2.count                    #result:3
print NewCounter2.__class__.count      #result:3
print NewCounter2.__class__.count is NewCounter2.count      #result: True

从上面的代码来看,我以为 NewCounter1.count 可能等于 NewCounter1,或者 __class__.count,但是接下来的代码让我感到惊讶:

NewCounter2.count = 5

print NewCounter1.count                 #result:3
print NewCounter1.__class__.count   #result:3
print NewCounter2.count                 #result:5
print NewCounter2.__class__.count   #result:3
print NewCounter2.__class__.count is NewCounter2.count       #result: False

为什么 NewCounter2.count 发生了变化,而 NewCounter2.__class__.count 仍然是 3?更有意思的是,当我改变了 NewCounter2.count 的值后,NewCounter2.__class__.count is NewCounter2.count 变成了 False。这个 __class__ 属性到底是什么呢?

4 个回答

3

如果你在一个对象上重新绑定(也就是给它赋值)一个和类里同名的属性,这样做会把类里的那个属性给遮住。也就是说,当你查找属性的时候,系统会先看看这个对象有没有这个属性,如果没有,再去查找它的类,按照类的继承顺序来找。

10

这一行:

NewCounter2.__class__.count = 3

改变了 counter 的静态 count,但是在这里:

NewCounter2.count = 5

NewCounter2 现在有了自己的 count 属性,这个属性遮住了静态的 count
所以那一行对 NewCounter1 没有影响。
这也是为什么 NewCounter2.__class__.count != NewCounter2.count 的原因。

23

“从上面的代码来看,我觉得 NewCounter1.count 可能等于 NewCounter1._class_.count。”

问题在于,在你提问的这句话时,经过唯一的指令:

NewCounter1 = counter()
NewCounter2 = counter()
NewCounter2.__class__.count = 3

创建了 NewCounter1NewCounter2
并且修改了类属性 counter.count
此时并不存在 NewCounter1.countNewCounter2.count,所以 “等于” 这个说法没有实际意义。

.

看看 NewCounter1 的创建过程,紧接着:

class counter:
    count = 0
    def __init__(self):
        self.__class__.count += 1

print 'counter.count BEFORE ==',counter.count  # The result is 0
NewCounter1 = counter()
print '\nNewCounter1.__dict__ ==',NewCounter1.__dict__  # The result is {}
print 'NewCounter1.count    ==',NewCounter1.count # The result is 1
print 'counter.count AFTER  ==',counter.count  # The result is 1

NewCounter._dict_ 是实例 NewCounter1 的命名空间
print NewCounter1.count 打印的内容和 print counter.count 是一样的
但是,'count'(字符串 'count')并不在 NewCounter1 的命名空间中,也就是说在这个实例中没有 count 这个属性!

这怎么可能呢?

这是因为实例是在没有给 'count' 这个标识符赋值的情况下创建的
-> 这意味着在 NewCounter1 中并没有真正创建任何作为字段的属性,也就是说没有创建实例属性。

结果是,当执行指令
print 'NewCounter1.count ==',NewCounter1.count
时,解释器在 NewCounter1 的命名空间中找不到实例属性,然后就去实例的类中查找这个类的命名空间里的 'count';在那里它找到了 'count' 作为一个类属性的键,并可以获取对象 counter.count 的值来显示作为指令的响应。

一个类的实例有一个命名空间,这个命名空间实现为字典,是查找属性引用的第一个地方。当在这里找不到某个属性时,如果实例的类中有同名属性,查找就会继续进行到类属性。

http://docs.python.org/reference/datamodel.html#the-standard-type-hierarchy

所以,NewCounter1.count 等于 NewCounter1.__class__.count 在这里的意思是,虽然 NewCounter1.count 并不真正存在,但它的值是类属性 NewCounter1.class.count 的值。在这里,“是”是英语动词,而不是用来测试两个对象身份的语言特性 is,它的意思是“被认为是”。

当执行 NewCounter2.__class__.count = 3 时,只有类属性 counter.count 受到影响。NewCounter1NewCounter2 的命名空间保持为空,查找 counter.count 的值的机制依然相同。

.

最后,当执行 NewCounter2.count = 5 时,这次在 NewCounter2 对象中创建了一个实例属性 count,并且 'count' 出现在 NewCounter2 的命名空间中。
这并不会覆盖任何东西,因为在实例的 __dict__ 中之前没有任何内容。
没有其他的变化会影响 NewCounter1counter.count

以下代码更清楚地展示了执行过程中的事件:

from itertools import islice

class counter:
    count = 0
    def __init__(self):
        print ('  |  counter.count   first == %d  at  %d\n'
               '  |     self.count   first == %d  at  %d')\
               % (counter.count,id(counter.count),
                  self.count,id(self.count))

        self.__class__.count += 1 # <<=====

        print ('  |  counter.count  second == %d  at  %d\n'
               '  |     self.count  second == %d  at  %d\n'
               '  |  id(counter) == %d   id(self) == %d')\
               % (counter.count,id(counter.count),
                  self.count,id(self.count),
                  id(counter),id(self))



def display(*li):
    it = iter(li)
    for ch in it:
        nn = (len(ch)-len(ch.lstrip('\n')))*'\n'
        x = it.next()
        print '%s ==  %s %s' % (ch,x,'' if '__dict__' in ch else 'at '+str(id(x)))



display('counter.count AT START',counter.count)


print ('\n\n----- C1 = counter() ------------------------')
C1 = counter()
display('C1.__dict__',C1.__dict__,
        'C1.count ',C1.count,
        '\ncounter.count ',counter.count)


print ('\n\n----- C2 = counter() ------------------------')
C2 = counter()
print ('  -------------------------------------------') 
display('C1.__dict__',C1.__dict__,
        'C2.__dict__',C2.__dict__,
        'C1.count ',C1.count,
        'C2.count ',C2.count,
        'C1.__class__.count',C1.__class__.count,
        'C2.__class__.count',C2.__class__.count,
        '\ncounter.count ',counter.count)


print '\n\n------- C2.__class__.count = 3 ------------------------\n'
C2.__class__.count = 3
display('C1.__dict__',C1.__dict__,
        'C2.__dict__',C2.__dict__,
        'C1.count ',C1.count,
        'C2.count ',C2.count,
        'C1.__class__.count',C1.__class__.count,
        'C2.__class__.count',C2.__class__.count,
        '\ncounter.count ',counter.count)


print '\n\n------- C2.count = 5 ------------------------\n'
C2.count = 5
display('C1.__dict__',C1.__dict__,
        'C2.__dict__',C2.__dict__,
        'C1.count ',C1.count,
        'C2.count ',C2.count,
        'C1.__class__.count',C1.__class__.count,
        'C2.__class__.count',C2.__class__.count,
        '\ncounter.count ',counter.count)

结果

counter.count AT START ==  0 at 10021628


----- C1 = counter() ------------------------
  |  counter.count   first == 0  at  10021628
  |     self.count   first == 0  at  10021628
  |  counter.count  second == 1  at  10021616
  |     self.count  second == 1  at  10021616
  |  id(counter) == 11211248   id(self) == 18735712
C1.__dict__ ==  {} 
C1.count  ==  1 at 10021616

counter.count  ==  1 at 10021616


----- C2 = counter() ------------------------
  |  counter.count   first == 1  at  10021616
  |     self.count   first == 1  at  10021616
  |  counter.count  second == 2  at  10021604
  |     self.count  second == 2  at  10021604
  |  id(counter) == 11211248   id(self) == 18736032
  -------------------------------------------
C1.__dict__ ==  {} 
C2.__dict__ ==  {} 
C1.count  ==  2 at 10021604
C2.count  ==  2 at 10021604
C1.__class__.count ==  2 at 10021604
C2.__class__.count ==  2 at 10021604

counter.count  ==  2 at 10021604


------- C2.__class__.count = 3 ------------------------

C1.__dict__ ==  {} 
C2.__dict__ ==  {} 
C1.count  ==  3 at 10021592
C2.count  ==  3 at 10021592
C1.__class__.count ==  3 at 10021592
C2.__class__.count ==  3 at 10021592

counter.count  ==  3 at 10021592


------- C2.count = 5 ------------------------

C1.__dict__ ==  {} 
C2.__dict__ ==  {'count': 5} 
C1.count  ==  3 at 10021592
C2.count  ==  5 at 10021568
C1.__class__.count ==  3 at 10021592
C2.__class__.count ==  3 at 10021592

counter.count  ==  3 at 10021592

.

一个有趣的事情是,在
self.count = counter.count
这一行之前添加一条指令
self.__class__.count += 1 # <<=====
来观察结果的变化。

.

总之,问题并不在于 __class__,而在于查找属性的机制,当忽视这个机制时会造成误解。

撰写回答