Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is python isinstance() transitive with base classes and intransitive with metaclasses?

I am new to metaclasses, and may be using them in unintended ways. I'm puzzled by the fact that the isinstance() method appears to have transitive behavior when dealing with subclasses, but not when dealing with metaclasses.

In the first case:

class A(object):
   pass

class B(A):
    pass

class C(B):
     pass

ic = C()

implies that isinstance(ic,X) is true for X equal to A, B or C.

On the other hand, here is a metaclass example:

class DataElementBase(object):
    def __init__(self,value):
        self.value = self.__initialisation_function__(value)

class MetaDataElement(type):
    def __new__(cls,name,initialisation_function, helptext ):
        result = type.__new__(cls,name,(DataElementBase,), dict(help=helptext) )
        result.__initialisation_function__  = staticmethod(initialisation_function)
        return result


###  test code  ####

# create a class from the metaclass
MyClass = MetaDataElement( 'myclass', float, "Value is obtained as float of user input" )

# create an instance of the class
my_instance = MyClass( '4.55' )

print ( 'MyClass is instance of MetaDataElement? %s' % isinstance( MyClass, MetaDataElement ) )
print ( 'MyClass is instance of DataElementBase? %s' % isinstance( MyClass, DataElementBase ) )
print ( 'my_instance is instance of MyClass? %s' % isinstance( my_instance, MyClass ) )
print ( 'my_instance is instance of MetaDataElement? %s' % isinstance( my_instance, MetaDataElement ) )
print ( 'my_instance is instance of DataElementBase? %s' % isinstance( my_instance, DataElementBase ) )

yields:

MyClass is instance of MetaDataElement? True
MyClass is instance of DataElementBase? False
my_instance is instance of MyClass? True
my_instance is instance of MetaDataElement? False
my_instance is instance of DataElementBase? True

That, is MyClass is an instance of the MetaDataElement metaclass, and my_instance is an instance of the MyClass class but not of MetaDataElement.

Is my interpretation correct? Is there a simple explanation for this?

like image 326
M Juckes Avatar asked Nov 07 '22 04:11

M Juckes


1 Answers

The confusion there is that MyClass is not an "instance" of DataElementBase. It is an instance of the metaclass: MetaDataElement, and an instance of all superclasses of that metaclass (that is: "type" and "object"). Just like what happens with instances of "ordinary" classes:

pasting your snippet in the interactive interpreter I can do:

In [96]: isinstance(MyClass, MetaDataElement)
Out[96]: True

In [97]: isinstance(MyClass, type)
Out[97]: True

In [98]: isinstance(MyClass, object)
Out[98]: True

In [99]: MyClass.__mro__
Out[99]: (__main__.myclass, __main__.DataElementBase, object)

The relationship of "DataElementBase" towards "MyClass" is of "superclass". So, if you ask if it is a subclass of DataElementBase, you get True:

In [100]: issubclass(MyClass, DataElementBase)
Out[100]: True

The class bases are passed as the third argument on the call to type.__new__.

So, in other words: a "metaclass" is the "class used to build a class", and it has nothing to do with the inheritance chain of the created class. And conversely, a given object will always be an instance of any "superclass" of its class. The metaclass is not a superclass of its class.

Once you understand that, you will notice that the behavior of "isinstance" and "issubclass" is the same across "non meta" classes and its instances, and "metaclasses" and the classes created through them.


On an unrelated notice, although your code work the way it is, it is not encouraged.: the signature for a metaclass __new__ method should be conformant to the signature of type.__new__ - and receive the metaclass itself, name, bases, namespace and optional keyword parameters.

The way you wrote it, departing your metaclass from this signature forces your code to declare classes the way you did: by explicitly instantiating the metaclass with a call. It can't be used as a metaclass that will create a new class from a a class statement and its body - as in this case, Python calls the metaclass with the parameters used by type.__new__.

like image 200
jsbueno Avatar answered Nov 14 '22 22:11

jsbueno