Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a parameterised type in Python, but make all instances children of the "super-type"

Suppose I have a Python type with the t property. I want to create a "parameterised metatype" such that the following works:

class MySuperClass(type):
    pass

class MySubClass(MySuperClass):
    # Here is the problem -- How do I define types that contain stuff,
    # independent of an object?
    def __init__(self, t): # Or __getitem__
        self.t = t

    def __instancecheck__(self, instance):
        return isinstance(instance, MySubClass) and instance.t == self.t

    def __subclasscheck__(self, subclass):
        return MySubClass in subclass.__mro__ and subclass.t == self.t

class MyObject(metaclass=MySubClass):
    def __init__(self, t):
        self.t = t

# Test code:
## Both of these, square brackets work too
assert isinstance(MyObject(0), MySubClass(0))
assert not isinstance(MyObject(0), MySubClass(1))

## Ideally
assert isinstance(MyObject(0), MySuperClass) or isinstance(MyObject(0), MySubClass)

Currently I get the following error:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-99ad08881526> in <module>
     14         return MySubClass in subclass.__mro__ and subclass.t == self.t
     15 
---> 16 class MyObject(metaclass=MySubClass):
     17     def __init__(self, t):
     18         self.t = t

TypeError: __init__() takes 2 positional arguments but 4 were given
like image 242
Hameer Abbasi Avatar asked Mar 03 '19 10:03

Hameer Abbasi


Video Answer


1 Answers

It is possible to meet the first part or requirement. But it will require an auxilliary checker class. A MySubClass is a descendant of type, MySubClass(0) should be a class. Its is enough to create an internal class InstanceChecker class in MySubClass, and put the __instancecheck__ override their.

Code could be:

class MySubClass(MySuperClass):
    def __new__(cls, name, bases=None, namespace=None, *args, **kwargs):
        if bases is not None:
            return super().__new__(cls, name, bases, namespace, **kwargs)
        return cls.InstanceChecker(name)

    class InstanceChecker:
        def __init__(self, t):
            self.t = t
        def __instancecheck__(self, instance):
            return isinstance(instance.__class__, MySubClass) and instance.t == self.t            

class MyObject(metaclass=MySubClass):
    def __init__(self, t):
        self.t = t

# Test code:
## Both of these, square brackets work too
assert isinstance(MyObject(0), MySubClass(0))
assert not isinstance(MyObject(0), MySubClass(1))

BTW, I have removed the __subclasscheck__ override, because t in only an instance attribute in MyObject


Alternatively, the metaclass can automatically add a super class in the bases parameter. In the following code, MySuperClass is no longer a superclass of MySubClass but of MyObject:

class MySuperClass():
    pass


class MySubClass(type):
    def __new__(cls, name, bases=None, namespace=None, *args, **kwargs):
        if bases is not None:
            return super().__new__(cls, name, bases + (MySuperClass,), namespace, **kwargs)
        return cls.InstanceChecker(name)
    class InstanceChecker:
        def __init__(self, t):
            self.t = t
        def __instancecheck__(self, instance):
            return isinstance(instance.__class__, MySubClass) and instance.t == self.t

class MyObject(metaclass=MySubClass):
    def __init__(self, t):
        self.t = t

# Test code:
## Both of these, square brackets work too
assert isinstance(MyObject(0), MySubClass(0))
assert not isinstance(MyObject(0), MySubClass(1))

## Ideally
assert isinstance(MyObject(0), MySuperClass)
like image 148
Serge Ballesta Avatar answered Sep 21 '22 05:09

Serge Ballesta