为什么`__subclasshook__`可以被monkeypatched到元类上,但`__instancecheck__`不能?

ely*_*ely 8 python interface subclass abc

这是一个尝试创建装饰器的玩具示例,该装饰器允许声明属性名称,这些属性名称应该是标准__subclasshook____instancecheck__模式中"接口检查"的必要部分.

当我装饰这个Foo类时,它似乎按预期工作.我创建了一个Bar与之无关的类,Foo但它具有所需的属性,并且它正确地满足isinstance(instance_of_bar, Foo) == True.

但是作为另一个例子,我创建了一个dict扩充的子类,以便key可以使用getattr语法访问这些值(例如dict,d['a']可以替换d.a为可获得相同结果的where ).在这种情况下,属性只是实例属性,因此__instancecheck__应该工作.

这是代码.请注意,鉴于具有Bar工作实例的示例,将__subclasshook__函数"monkeypatch"选择到Foo类(具有元类)中的选择正常.因此,似乎不必直接在元类的类定义中定义函数.

#Using Python 2.7.3

import abc
def interface(*attributes):
    def decorator(Base):

        def checker(Other):
            return all(hasattr(Other, a) for a in attributes)

        def __subclasshook__(cls, Other):
            if checker(Other):
                return True
            return NotImplemented

        def __instancecheck__(cls, Other):
            return checker(Other)

        Base.__subclasshook__ = classmethod(__subclasshook__)
        Base.__instancecheck__ = classmethod(__instancecheck__)
        return Base

    return decorator

@interface("x", "y")
class Foo(object):
    __metaclass__ = abc.ABCMeta
    def x(self): return 5
    def y(self): return 10

class Bar(object):
    def x(self): return "blah"
    def y(self): return "blah"

class Baz(object):
    def __init__(self):
        self.x = "blah"
        self.y = "blah"

class attrdict(dict):
    def __getattr__(self, attr):
        return self[attr]

f = Foo()
b = Bar()
z = Baz()
t = attrdict({"x":27.5, "y":37.5})

print isinstance(f, Foo)
print isinstance(b, Foo)
print isinstance(z, Foo)
print isinstance(t, Foo)
Run Code Online (Sandbox Code Playgroud)

打印:

True
True
False
False
Run Code Online (Sandbox Code Playgroud)

这只是一个玩具示例 - 我不是在寻找更好的方法来实现我的attrdict课程.该Bar示例演示了monkeypatched __subclasshook__工作.另外两个示例演示了__instancecheck__仅具有实例属性的实例的检查失败.在那些情况下,__instancecheck__甚至没有被召唤.

我可以手动检查,从我的病情__instancecheck__功能是通过实例满意attrdict(即,hasattr(instance_of_attrdict, "x")True因为需要)或z.

再次,它似乎适用于实例Bar.这表明__subclasshook__装饰器正在应用,并且修补自定义__metaclass__不是问题.但__instancecheck__似乎并未在此过程中调用.

为什么可以__subclasshook__在元类的类定义之外定义并在以后添加,但不是__instancecheck__

Sim*_*agi 2

一切都按应有的方式进行。如果您使用__metaclass__,您将覆盖类创建过程。看起来你的 Monkeypatch 正在工作,__subclasshook__但它只是__subclasshook__ABCMeta. 你可以用这个来检查:

>>> type(Foo)
<class 'abc.ABCMeta'>
Run Code Online (Sandbox Code Playgroud)

更明确地说:__subclasshook__在这个例子中,这种情况是偶然发生的,因为元类在某些情况下__subclasscheck__碰巧遵循类的__subclasshook__。元类的协议__instancecheck__永远不会遵循类的定义__instancecheck__,这就是为什么最终会调用 Monkeypatched 版本,但不会调用__subclasshook__Monkeypatched 版本。__instancecheck__

更详细地说:如果您使用元类创建类,则该类的类型将是元类。在这种情况下ABCMeta,. 其定义isinstance()如下:'isinstance(object, class-or-type-or-tuple) -> bool',这意味着实例检查将在给定的类、类型或类/类型的元组上执行。ABCMeta在这种情况下, isinstance 检查将在(ABCMeta.__instancecheck__()将被调用 )上完成。因为 Monkeypatch 应用于Foo类而不是ABCMeta__instancecheck__所以 的方法Foo永远不会运行。但是 的__instancecheck__ABCMethod调用它自己的方法,而第二个方法将通过执行创建的类的方法__subclasscheck__来尝试验证(在本例中)。__subclasshook__Foo

在这种情况下,如果您像这样覆盖元类的函数,则可以获得所需的行为:

def interface(*attributes):
    def decorator(Base):

        def checker(Other):
            return all(hasattr(Other, a) for a in attributes)

        def __subclasshook__(cls, Other):
            if checker(Other):
                return True
            return NotImplemented

        def __instancecheck__(cls, Other):
            return checker(Other)

        Base.__metaclass__.__subclasshook__ = classmethod(__subclasshook__)
        Base.__metaclass__.__instancecheck__ = classmethod(__instancecheck__)
        return Base

    return decorator
Run Code Online (Sandbox Code Playgroud)

以及带有更新代码的输出:

True
True
True
True
Run Code Online (Sandbox Code Playgroud)

另一种方法是定义您自己的类作为元类,并创建__instancecheck__您正在寻找的协议类型,以便当元类的定义达到某些失败标准时,它确实遵循类的定义__instancecheck__。然后,设置__metaclass__为内部的类Foo并且您现有的装饰器应该按原样工作。

更多信息:关于元类的好文章