-1

I'm learning knowledge about metaclass recently. I learnt that isinstance(object, type) and issubclass(type, object). I want to write self-defined class act like object and type, but how to declare class when circular dependency is happened? the pseudo code is as follow:

declare class MyType

class MyObject(metaclass=MyType):
    pass

class MyType(MyObject, type):
    pass

jsbueno
  • 99,910
  • 10
  • 151
  • 209
wangjianyu
  • 97
  • 4
  • 3
    You can't, not in Python. You might be able to with a C extension, but Python uses *bootstrapping* to set up that dependency, which involves mutating internal data that isn't, under normal circumstances, meant to be mutated. – Silvio Mayolo May 15 '23 at 15:08
  • I don't know why this was closed. Neither of the suggested questions are duplicates of this. Just because they all deal with metaclasses does not mean this is not a valid question unto itself, albeit being poorly worded. – Daniil Fajnberg May 15 '23 at 19:50
  • Sure - this is not a duplicate. Reopening. Someone _might_ come up with some workaround to do what the OP is asking for. Not that I can imagine it being of any practical use - but then, I guess a lot of metaclass questions are for learning purposes. – jsbueno May 16 '23 at 14:23

1 Answers1

0

As stated in the very appropriate comment above:

""" You can't, not in Python. You might be able to with a C extension, but Python uses bootstrapping to set up that dependency, which involves mutating internal data that isn't, under normal circumstances, meant to be mutated. – Silvio Mayolo """.

Well, that, while correct, is not entirely true - Python dynamicism allows one to change, in a limited form, classes bases and inheritance, and even object classes, after a class or object is created.

That means that part of the same bootstrapping can be performed in pure Python code.

There is a second hard-coded relationship in Python that you don't mention in your question: type is own metaclass - that is type is an instance of type: that really can't be emulated in pure Python code. The mutual relationship of type and object however, can. Again, that is of no practical use whatsoever, and object and type are always kept in the loop as baseclass and base metaclass: you can't really do anything without them.

That said, check the examples bellow.

First thing is that type is quite unique, not due to this relationship with object, but because it does all the job for actually creating a class in Python under the hood - and that is a lot of things. Eventually all custom metaclasses have at some point to call type.__new__ to actually create a type - so while your MyType can be made to inherit from MyObject, it will also have to retain inheritance from type (as one of the bases) - so that class creation job can be done. (ok - eventually, there is a way out of it, with overriding the right methods, and proxying calls to type - but lets deffer that).

Fact is: to gain this egg-hen relation ship you have to change at least one of your objects after it is created.

It is not possible to modify a class` class (its metaclass) after it is created:

class A: pass

class M(A, type): pass

A.__class__ = M

Will raise TypeError: __class__ assignment only supported for mutable types or ModuleType subclasses . But the converse is actually possible:

class M(type): pass

class A(metaclass=M): pass

M.__bases__ = (A, type)

As I wrote above, one needs to keep type as one of the bases for M - trying to remove it from the bases in the assignment, will raise: TypeError: __bases__ assignment: 'A' object layout differs from 'type' . But doing M.__bases__ = (A, type) works, and after doing that:

In [18]: isinstance(A, M)
Out[18]: True

In [19]: issubclass(M, A)
Out[19]: True

Now, for the second part, if you don't want M to inherit from type at all, just implementing __new__ and __call__ on M suffices for having a "working metaclass". HOWEVER, there is no way then to pass M as the first parameter in the call to type.__new__: Python checks internally that the metaclass passed in this call is a "real" subclass of type. This means that when you ask for the class of A subsequently, it won't say it is a "class of M" using normal methods - but then, we can "falsify" that somewhat, by implementing the __instancecheck__ special method in the "metaclass of the metaclass".

Note that due to some of the restrictions in __bases__ assignmen, and to the fact that __instancecheck__ must live on the metahclass of the class whose instance we want to check (so, in the metaclass of our metaclass), we need 2 other intermediate classes now.

The code in __instancecheck__ and __subclasscheck__ bellow can be tweaked to perform some "real checking" - just returning "True" is enough for demonstration purposes:

class A: pass


class M(type):
    def __instancecheck__(self, instance):
        return True
    def __subclasscheck__(self, subclass):
        return True
    
class N(A, metaclass=M):
    def __new__(mcls, name, bases, ns):
        cls = type.__new__(type, name, bases, ns) # <- here, M can't be the first argument, as it does not inherit from "type"
        return cls
    def __call__(cls, *args, **kw):
        instance = cls.__new__(cls, *args, **kw)
        if isinstance(instance, cls):
            instance.__init__(*args, **kw)
        return instance
        
class B(metaclass=N): pass

N.__bases__ = (N,)

And now, with a metaclass not inheriting from type, you can have:

In [42]: isinstance(B, N)
Out[42]: True

In [43]: issubclass(N, B)
Out[43]: True

However, if the test is made through type you won't see N as the metaclass:

In [44]: type(B)
Out[44]: type
jsbueno
  • 99,910
  • 10
  • 151
  • 209