![](/img/trans.png)
[英]Why __instancecheck__ is not always called depending on argument?
[英]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.