2017-12-10 7 views
4

Ich habe folgenden python3 Code:Warum wird __instancecheck__ nicht aufgerufen?

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)) 

und wenn ich es auf Python 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32 laufen bekomme ich folgende Ausgabe

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

Warum es nicht doing instance check ausgibt? Die documentation sagt die __instancecheck__ Methode muss auf der Metaklasse und nicht die Klasse selbst definiert werden, die ich hier getan habe. Ich überprüfe sogar, dass die Metaklasse verwendet wird, da creating class 'A' gedruckt wird. Wenn ich isinstance aufrufen, scheint es jedoch die Standardimplementierung zu verwenden und nicht die, die ich in der Metaklasse definiert habe.

Ich verwende wahrscheinlich keine Metaklassen korrekt, aber ich kann nicht herausfinden, wo ich meinen Fehler gemacht habe.

+0

Lassen Sie uns versuchen, dieses "Implementierungsdetail" zu umgehen ... – jsbueno

+1

nur als eine ungerade Randnotiz, wenn Sie '__instancecheck__' als Methode für eine normale Klasse definieren, können Instanzen der Klasse als zweites Argument von' verwendet werden issinstance' – drdrez

Antwort

5

Die Funktion isinstance überprüft schnell, ob der Typ der Instanz, die als Argument übergeben wird, derselbe ist wie der der Klasse. Wenn dies der Fall ist, kehrt es früh zurück und ruft Ihre benutzerdefinierte __instancecheck__ nicht auf.

Dies ist eine Optimierung, die verwendet wird, um einen teuren Aufruf an (es ist Pythonland-Code) zu vermeiden, wenn es nicht erforderlich ist.

Sie können die specific test in PyObject_IsInstance, die Funktion sehen, die den isinstance Aufruf in der CPython Implementierung behandelt:

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

Natürlich Ihr __instancecheck__ Feuer richtig, wenn dieser Test nicht True ist:

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

Ich bin mir nicht sicher, ob das implementationsspezifisch ist, würde ich mir aber denken, da es in the corresponding PEP section noch in der Dokumentation aufkeinen Verweis darauf gibt.


Interessante beiseite: issubclass verhält sich eigentlich nicht auf diese Weise. Aufgrund seiner Implementierung ruft es immer __subclasscheck__. Ich hatte an issue vor einer Weile geöffnet, die noch aussteht.

+1

Shoot, ich hätte gedacht, dass dies der Fall war und versuchte es mit ein paar anderen Fällen zu testen. Vielen Dank! – drdrez

+1

Huh. Das scheint der Dokumentation zu widersprechen. Die [Datenmodelldokumente] (https://docs.python.org/3/reference/datamodel.html#customizing-instance-and-subclass-checks) für "__instancecheck__" und "__subclasscheck__" erwähnen diesen Fall ebenfalls nicht . Ich fühle mich wie die 'if (Py_TYPE (inst) == (PyTypeObject *) cls)' Prüfung sollte in den 'if (PyType_CheckExact (cls)) 'Block verschoben werden; es würde besser zu den Dokumenten passen und es würde die Dinge wahrscheinlich nicht wesentlich verlangsamen, obwohl ich es nicht getestet habe. – user2357112

+0

Es ist eine seltsame Situation, weil es auch eine Diskrepanz zwischen "__instancecheck__" und "__subclasscheck__" @ user2357112 gibt. Ich hatte vor einiger Zeit ein [Problem] (https://bugs.python.org/issue30230) geöffnet, wo ich über ihren Unterschied sprach (den ich noch nicht auf python-dev veröffentlicht habe). –

3

Jims Antwort scheint es zu nageln.

Aber wer Bedürfnisse aus irgendeinem weid Grunde ein vollständig angepasst instancheck (jetzt ok, dass ich dies schreibe, scheint es kein richtiger Grund für den man, dass wollen, lassen hoffen, dass ich falsch bin) , eine Metaklasse kann davon mitnehmen, aber es ist schwierig.

Dieser ersetzt dynamisch die tatsächliche Klasse des Objekts, das von einer "Schattenklasse" instanziiert wird, die ist ein Klon des Originals. Auf diese Weise schlägt der native "Instancheck" immer fehl, und die Metaklasse wird aufgerufen.

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)) 

Es hat sogar einen Mechanismus zum Synchronisieren von Attributen in der Schattenklasse und tatsächlichen Klasse. Die einzige Sache, die es nicht unterstützt, ist, wenn Klassen, die auf diese Weise behandelt werden, eine benutzerdefinierte __new__ implementieren.Wenn ein solcher __new__ Parameterless super verwendet, wird es schwierig, als der Parameter zu Super wäre nicht die Schattenklasse.

+0

Wow, das ist über und jenseits! Danke für diesen Hack! – drdrez

Verwandte Themen