簡體   English   中英

為什么不調用__instancecheck__?

[英]Why isn't __instancecheck__ being called?

我有以下python3代碼:

class BaseTypeClass(type):
    def __new__(cls, name, bases, namespace, **kwd):
        result = type.__new__(cls, name, bases, namespace)
        print("creating class '{}'".format(name))
        return result

    def __instancecheck__(self, other):
        print("doing instance check")
        print(self)
        print(other)
        return False


class A(metaclass=BaseTypeClass):
    pass

print(type(A))
print(isinstance(A(), A))

當我在Python 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32上運行Python 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32我得到以下輸出

creating class 'A'
<class '__main__.BaseTypeClass'>
True

為什么不輸出doing instance check 文檔__instancecheck__方法需要在元類上定義,而不是類本身,我在這里做了。 我甚至驗證正在使用元類,因為creating class 'A' 但是,當我調用isinstance它似乎使用的是默認實現,而不是我在元類中定義的實現。

我可能沒有正確使用元類,但我無法弄清楚我犯了哪些錯誤。

isinstance函數快速檢查以查看作為參數提供的實例的類型是否與類的實例類型相同。 如果是這樣,它會提前返回,並且不會調用您的自定義__instancecheck__

這是一種優化,用於避免在__instancecheck____instancecheck__ (它是Pythonland代碼)進行昂貴的調用。

您可以PyObject_IsInstance看到特定的測試, PyObject_IsInstance是在CPython實現中處理isinstance調用的函數:

/* Quick test for an exact match */
if (Py_TYPE(inst) == (PyTypeObject *)cls)
    return 1;

當然,當該測試不為True時, __instancecheck__會正確觸發:

>>> isinstance(2, A)
doing instance check
<class '__main__.A'>
2
False

我不確定這是否是特定於實現的,但我想是這樣的,因為在相應的PEP部分isinstance的文檔中沒有對此的引用。


有趣的是: issubclass實際上並不是這樣的。 由於它的實現,它總是調用__subclasscheck__ 我已經在這一段時間內打開了一個問題 ,這個問題仍然懸而未決。

吉姆的回答似乎很明確。

但是對於那些需要一些weid的人來說,完全定制的instancheck(好吧,現在我正在寫這個,似乎沒有正確的理由讓人想要那個,我希望我錯了),一個元類可以逃脫它,但這很棘手。

這個動態地替換了由“影子​​類”實例化的對象的實際類,“影子類”是原始的克隆。 這樣,本機“instancheck”總是失敗,並且調用元類一。

def sub__new__(cls, *args, **kw):
    metacls = cls.__class__
    new_cls = metacls(cls.__name__, cls.__bases__, dict(cls.__dict__), clonning=cls)
    return new_cls(*args, **kw)

class M(type):
    shadows = {}
    rev_shadows = {}
    def __new__(metacls, name, bases, namespace, **kwd):
        clonning = kwd.pop("clonning", None)
        if not clonning:
            cls = super().__new__(metacls, name, bases, namespace)
            # Assumes classes don't have  a `__new__` of them own.
            # if they do, it is needed to wrap it.
            cls.__new__ = sub__new__
        else:
            cls = clonning
            if cls not in metacls.shadows:
                clone = super().__new__(metacls, name, bases, namespace)
                # The same - replace for unwrapped new.
                del clone.__new__
                metacls.shadows[cls] = clone
                metacls.rev_shadows[clone] = cls
            return metacls.shadows[cls]

        return cls

    def __setattr__(cls, attr, value):

        # Keep class attributes in sync with shadoclass
        # This could be done with 'super', but we'd need a thread lock
        # and check for re-entering.
        type.__setattr__(cls, attr, value)
        metacls = type(cls)
        if cls in metacls.shadows:
            type.__setattr__(metacls.shadows[cls], attr, value)
        elif cls in metacls.rev_shadows:
            type.__setattr__(metacls.rev_shadows[cls], attr, value)    

    def call(cls, *args, **kw):
        # When __new__ don't return an instance of its class,
        # __init__ is not called by type's __call__
        instance = cls.__new__(*args, **kw)
        instance.__init__(*args, **kw)
        return instance

    def __instancecheck__(cls, other):
        print("doing instance check")
        print(cls)
        print(other)
        return False


class A(metaclass=M):
    pass

print(type(A))
print(isinstance(A(), A))

它甚至在shadow類和實際類中都有一個機制來同步屬性。 它不支持的一件事是,以這種方式處理的類確實實現了自定義__new__ 如果這樣的__new__使用無參數super ,它開始變得棘手,因為super的參數不是shadow類。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM