type(instance)和instance.__class__有何不同?

13

Python内置了函数type

class type(object)

接受一个参数,返回这个对象的类型。返回值是一个类型对象,并且通常与object.__class__返回的对象相同。

Python还有特殊的属性__class__

instance.__class__

指实例所属的类。

我曾经认为它们都指向同一个对象。但在方法abc.ABCMeta.__instancecheck__中会检查它们是否相同:

    def __instancecheck__(cls, instance):
        """Override for isinstance(instance, cls)."""
        # Inline the cache checking
        subclass = instance.__class__
        # […]
        subtype = type(instance)
        if subtype is subclass:
        # […]

什么情况下 type(instance)instance.__class__ 不同?


1
可能是重复的问题 https://dev59.com/mWkw5IYBdhLWcg3w1eG7 - Mazdak
1
@Kasramvd 感谢您提供的链接,我认为它可能是 https://dev59.com/Q3NA5IYBdhLWcg3wL6sc 的重复。我不确定为什么在搜索问题时它们没有出现,也没有在“建议的重复项”中出现。 - MSeifert
2个回答

4

type(instance)instance.__class__可能会有所不同,即使使用新式类,正如Guido van Rossum在PEP 3119中提到的:

此外,isinstance(x, B)等同于issubclass(x.__class__, B) or issubclass(type(x), B)。(当x是代理对象时,type(x)x.__class__可能不是同一个对象。)

例如,标准库的weakref.proxy函数创建代理对象。

>>> import weakref
>>> class A: pass
... 
>>> a = A()
>>> type(weakref.proxy(a))
<class 'weakproxy'>
>>> weakref.proxy(a).__class__
<class '__main__.A'>
>>> repr(weakref.proxy(a))
'<weakproxy at 0x10065ab30 to A at 0x1006534c0>'

请注意,代理对象的__repr__方法的实现使用type(instance)而不是instance.__class__,因为__repr__方法的主要目的是在调试时提供足够的信息以重新创建对象。 type(instance) 一个object实例的真实类存储在实例的__class__ slot中(即在实例布局中的固定偏移量)。它只能通过data descriptor vars(object)['__class__'](其方法__get__允许属性检索,其方法__set__允许属性赋值,其方法__delete__禁止属性删除)或等效地通过内置函数type(其一参数形式允许属性检索)来访问:
>>> class A: pass
... 
>>> a = A()
>>> type(a)
<class '__main__.A'>
>>> vars(object)['__class__'].__get__(a)
<class '__main__.A'>
>>> class B: pass
... 
>>> vars(object)['__class__'].__set__(a, B)
>>> type(a)
<class '__main__.B'>
>>> vars(object)['__class__'].__get__(a)
<class '__main__.B'>
>>> vars(object)['__class__'].__delete__(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't delete __class__ attribute

instance.__class__

如果在 object 的子类中没有覆盖数据描述符 vars(object)['__class__'],那么 instance.__class__ 通过数据描述符访问实例的真实类:
>>> class A: pass
... 
>>> a = A()
>>> type(a)
<class '__main__.A'>
>>> a.__class__
<class '__main__.A'>
>>> class B: pass
... 
>>> a.__class__ = B
>>> type(a)
<class '__main__.B'>
>>> a.__class__
<class '__main__.B'>
>>> del a.__class__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't delete __class__ attribute

但是,如果在一个object子类中覆盖了数据描述符vars(object)['__class__'],那么instance.__class__将无法访问instance的真实类。此外,如果覆盖不是一个数据描述符,它本身可以在instance中被覆盖:

>>> class A: __class__ = int  # overrides vars(object)['__class__']
... 
>>> a = A()
>>> type(a)
<class '__main__.A'>
>>> a.__class__
<class 'int'>
>>> a.__class__ = str  # overrides vars(A)['__class__'] (not a data descriptor)
>>> type(a)
<class '__main__.A'>
>>> a.__class__
<class 'str'>
>>> del a.__class__
>>> type(a)
<class '__main__.A'>
>>> a.__class__
<class 'int'>

1
无法使用实例属性覆盖数据描述符。 - Martijn Pieters

1
这适用于旧式对象(未继承任何内容)。这些对象没有__class__属性。我认为他们这样做是为了防止错误。Python 2.7的示例:
class A:
    pass

class B(object):
    pass

a = A()
b = B()

print(dir(a)) # ['__doc__', '__module__']
print(dir(b)) # ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

print(b.__class__) # <class '__main__.B'>
print(type(b))     # <class '__main__.B'>

#####################
# The intersting part
print(a.__class__) # __main__.A
print(type(a))     # <type 'instance'>


print(B.__class__) # <type 'type'>
print(type(B))     # <type 'type'>

print(type(A))      # <type 'classobj'>
#print(A.__class__) # AttributeError: class A has no attribute '__class__'

请参阅以下内容以了解更多信息:

注意:这些来自cpython的代码行在2008年最后一次更改(提交),因此看起来这真的是一个兼容性问题,或者他们只是忘记了它。


虽然这很有道理 - 我在考虑(忘了明确提到)Python 3.x,现在已经没有旧式类了。 - MSeifert

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