问题
I have the following python3 code:
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))
and when I run it on Python 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
I get the following output
creating class 'A'
<class '__main__.BaseTypeClass'>
True
Why isn't it outputting doing instance check
? The documentation says the __instancecheck__
method needs to be defined on the metaclass and not the class itself, which I have done here. I even verify the metaclass is being used since creating class 'A'
is printed. However, when I call isinstance
it appears to be using the default implementation and not the one I defined in the metaclass.
I'm probably not using metaclasses correctly, but I can't figure out where I made my mistake.
回答1:
The isinstance
function makes a quick check to see if the type of the instance supplied as an argument is the same as that of the class. If so, it returns early and doesn't invoke your custom __instancecheck__
.
This is an optimization used in order to avoid an expensive call to __instancecheck__
(it's Pythonland code) when it isn't required.
You can see the specific test in PyObject_IsInstance, the function that handles the isinstance
call in the CPython implementation:
/* Quick test for an exact match */
if (Py_TYPE(inst) == (PyTypeObject *)cls)
return 1;
Of course, your __instancecheck__
fires correctly when that test isn't True
:
>>> isinstance(2, A)
doing instance check
<class '__main__.A'>
2
False
I am not certain if this is implementation specific, I would of thought so, though, since there's no reference to this in the corresponding PEP section nor in the documentation on isinstance
.
Interesting aside: issubclass
actually doesn't behave this way. Due to its implementation it always calls __subclasscheck__
. I had opened an issue on this a while back which is still pending.
回答2:
Jim's answer seems to nail it.
But for whoever needs for some weid reason a fully customized instancheck (ok, now that I am writing this, there seems to be no correct reason for one to want that, let s hope I am wrong), a metaclass can get away with it, but it is tricky.
This one dynamically replaces the actual class of the object being instantiated by a "shadow class", that is a clone of the original. This way, the native "instancheck" always fail, and the metaclass one is called.
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))
It even has a mechanism do sync attributes in the shadow class and actual class. The one thing it does not support is if classes handled in this way do implement a custom __new__
. If such a __new__
makes use of parameterless super
, it starts to become tricky, as the parameter to super would not be the shadow class.
来源:https://stackoverflow.com/questions/47740953/why-isnt-instancecheck-being-called