【问题标题】:Why is python isinstance() transitive with base classes and intransitive with metaclasses?为什么 python isinstance() 对基类传递而对元类传递不传递?
【发布时间】:2021-01-29 08:44:39
【问题描述】:

我是元类的新手,可能会以意想不到的方式使用它们。我很困惑isinstance() 方法在处理子类时似乎具有传递行为,但在处理元类时却没有。

第一种情况:

class A(object):
   pass

class B(A):
    pass

class C(B):
     pass

ic = C()

暗示isinstance(ic,X) 为真,X 等于 ABC

另一方面,这是一个元类示例:

class DataElementBase(object):
    def __init__(self,value):
        self.value = self.__initialisation_function__(value)

class MetaDataElement(type):
    def __new__(cls,name,initialisation_function, helptext ):
        result = type.__new__(cls,name,(DataElementBase,), dict(help=helptext) )
        result.__initialisation_function__  = staticmethod(initialisation_function)
        return result


###  test code  ####

# create a class from the metaclass
MyClass = MetaDataElement( 'myclass', float, "Value is obtained as float of user input" )

# create an instance of the class
my_instance = MyClass( '4.55' )

print ( 'MyClass is instance of MetaDataElement? %s' % isinstance( MyClass, MetaDataElement ) )
print ( 'MyClass is instance of DataElementBase? %s' % isinstance( MyClass, DataElementBase ) )
print ( 'my_instance is instance of MyClass? %s' % isinstance( my_instance, MyClass ) )
print ( 'my_instance is instance of MetaDataElement? %s' % isinstance( my_instance, MetaDataElement ) )
print ( 'my_instance is instance of DataElementBase? %s' % isinstance( my_instance, DataElementBase ) )

产量:

MyClass is instance of MetaDataElement? True
MyClass is instance of DataElementBase? False
my_instance is instance of MyClass? True
my_instance is instance of MetaDataElement? False
my_instance is instance of DataElementBase? True

也就是说,MyClassMetaDataElement 元类的实例,my_instanceMyClass 类的实例,但不是 MetaDataElement 的实例。

我的解释正确吗?对此有简单的解释吗?

【问题讨论】:

  • 因为子类是相关类型。元类是类型的类型。不是一回事,你不应该期望实例是 metaclass 的实例,它是 class 的实例,而类是元类。
  • 是的,您的解释是正确的。定义上是真的;子类化与元类不同(因此名称不同)。简单来说my_instanceMyClass类型,没有MetaDataElement类型,所以不是MetaDataElement的实例。
  • 这里没有惊喜。由于您已经回答了自己的问题,因此不确定除了“是”之外任何人都可以给出什么有用的答案。
  • 好的,问题不清楚。我已将 jsbueno 的答案标记为正确,因为它添加了更多上下文,帮助我了解更多。

标签: python metaclass


【解决方案1】:

令人困惑的是MyClass 不是DataElementBase 的“实例”。它是元类的一个实例:MetaDataElement,以及该元类的所有超类的一个实例(即:“类型”和“对象”)。就像“普通”类的实例发生的情况一样:

将你的 sn-p 粘贴到我可以做的交互式解释器中:

In [96]: isinstance(MyClass, MetaDataElement)
Out[96]: True

In [97]: isinstance(MyClass, type)
Out[97]: True

In [98]: isinstance(MyClass, object)
Out[98]: True

In [99]: MyClass.__mro__
Out[99]: (__main__.myclass, __main__.DataElementBase, object)

“DataElementBase”与“MyClass”的关系是“超类”。所以,如果你问它是否是 DataElementBase 的子类,你会得到 True:

In [100]: issubclass(MyClass, DataElementBase)
Out[100]: True

类基在调用type.__new__时作为第三个参数传递。

因此,换句话说:“元类”是“用于构建类的类”,它与创建的类的继承链无关。相反,给定对象将始终是其类的任何“超类”的实例。元类不是其类的超类。

一旦您理解了这一点,您就会注意到“isinstance”和“issubclass”的行为在“非元”类及其实例以及“元类”和通过它们创建的类之间是相同的。


在不相关的通知中,尽管您的代码按原样工作,但不鼓励这样做。:元类 __new__ 方法的签名应该与 type.__new__ 的签名一致 - 并接收元类本身,名称、bases、命名空间和可选的关键字参数。

按照您编写它的方式,将元类与此签名分开会强制您的代码以您所做的方式声明类:通过调用显式实例化元类。它不能用作从 class 语句及其主体创建新类的元类 - 在这种情况下,Python 使用 type.__new__ 使用的参数调用元类。

【讨论】:

  • 谢谢,我认为这回答了我的问题,并为我提供了一个很好的学习点。它还解释了为什么我添加到 MetaDataElement 的方法(不包括在上面的 sn-p 中)被 MyClass 继承,但不被 MyClass 的实例继承(尽管这可能与我使用不鼓励__new__ 的签名)。
  • 确实 - 元类上的方法从类本身可见,但从其实例中不可见。这是因为 Python 对属性的搜索首先到达实例类,然后,如果它不是那里的“数据描述符”,Python 会在实例中搜索,然后返回类,然后从那里搜索类的超类。属性搜索永远不会进入元类。可以故意使用它来拥有实例中不可用的类方法。
【解决方案2】:

isinstance 的基本目的是检查一个对象是否有一个接口。例如,(类型对象)DataElementBase 的接口可能包括它具有value 属性。确实,my_instance.value 是 4.55。

(类型的对象)MetaDataElement 的接口是它是一个,除其他外,它继承自DataElementBasemy_instance是这样的一类吗?不;它甚至不像类那样可调用。它是这样一个类的对象,但isinstance(my_interface, MetaDataElement) 暗示它这样一个类。

【讨论】:

  • 这是不正确的。 -1。 isinstance 检查对象是否是给定类的实例(其直接类或其任何超类都符合条件)。期间。
  • @jsbueno:我是否反驳了那个说法?问题是为什么 isinstance 有它的行为,而不是什么行为在实现意义上。
  • isinstance 不检查接口。语言本身不知道“接口” - 也许最近的静态类型协议除外。 isinstance 只是检查一个对象是否是一个类的实例(哇)——包括其直接类的所有超类,包括虚拟超类。定义接口的属性、方法和其他东西可能会或可能不会沿着该链继承:isinstance 不会也不会关心它们。
  • 即使在这种情况下,OP 的元类也会通过对 __new__ 方法使用不兼容的签名来打破“元类接口”的含义。
  • @jsbueno:当然没有语言级别的接口,你可以跳过调用基类构造函数。这并没有改变任何事情:我正在解释这个原则——特别是 Liskov 替换原则——它促使拥有一个谓词来表示“这个对象的类型是 X 还是从 X 派生的类?” (但不鼓励考虑元类)。对于不同的问题(“isinstance 检查什么?”),这些详细信息将是答案——以及诸如 __instancecheck__ 之类的内容。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-11
  • 2016-09-03
相关资源
最近更新 更多