【问题标题】:When is type(instance) different from instance.__class__?type(instance) 何时与 instance.__class__ 不同?
【发布时间】:2017-07-12 02:51:02
【问题描述】:

Python有内置函数type

type(对象)

使用一个参数,返回一个对象的类型。返回值是一个类型对象,一般与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__ 不同?

【问题讨论】:

标签: python class types


【解决方案1】:

type(instance)instance.__class__ 可能不同,即使使用 new-style classes,正如 Guido van Rossum 在PEP 3119 中提到的那样:

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

type(instance)

object 实例的真实类存储在实例上的 __class__ 中(即在实例布局中的固定偏移处)。只能通过数据描述符vars(object)['__class__']访问(其方法__get__允许属性检索,其方法__set__允许属性赋值,其方法__delete__禁止属性删除),或者等效地通过内置函数访问type(其单参数形式允许属性检索):

>>> class A: pass
... 
>>> a = A()
>>> vars(object)['__class__'].__get__(a)
<class '__main__.A'>
>>> type(a)
<class '__main__.A'>
>>> class B: pass
... 
>>> vars(object)['__class__'].__set__(a, B)
>>> vars(object)['__class__'].__get__(a)
<class '__main__.B'>
>>> type(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__

所以如果一个实例没有覆盖数据描述符vars(object)['__class__'](带有类属性),instance.__class__会访问instance的真实类(通过数据描述符):

>>> class A: pass
... 
>>> a = A()
>>> a.__class__
<class '__main__.A'>
>>> type(a)
<class '__main__.A'>
>>> class B: pass
... 
>>> a.__class__ = B
>>> a.__class__
<class '__main__.B'>
>>> type(a)
<class '__main__.B'>
>>> del a.__class__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't delete __class__ attribute

但是如果一个实例覆盖了数据描述符vars(object)['__class__'](带有类属性),instance.__class__可能无法访问instance的真实类(所以instance作为另一个类的实例出现):

>>> class A: __class__ = int
... 
>>> a = A()
>>> a.__class__
<class 'int'>
>>> type(a)
<class '__main__.A'>
>>> vars(a).update({'__class__': str})
>>> a.__class__ # the class attribute is not a data descriptor so is overridden
<class 'str'>
>>> type(a)
<class '__main__.A'>

【讨论】:

  • 你不能用实例属性覆盖数据描述符。
  • @MartijnPieters 很好,谢谢。固定。
【解决方案2】:

这是旧样式对象的情况(从无继承)。此类对象没有__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 年更改的 (commit),所以这似乎是一个兼容性问题,或者他们只是忘记了。

【讨论】:

  • 虽然这很有意义 - 我正在考虑(忘记明确提及)关于 python 3.x 并且不再有旧式类。
  • @MSeifert 上次 cpython 中给定的行在 2008 年被更改(github.com/python/cpython/commit/…),所以这似乎是一个兼容性问题,或者他们只是忘记了。
猜你喜欢
  • 1970-01-01
  • 2011-10-03
  • 1970-01-01
  • 2016-05-31
  • 1970-01-01
  • 2013-07-03
  • 1970-01-01
  • 2014-03-05
相关资源
最近更新 更多