Say I want to implement a metaclass that should serve as a class factory. But unlike the type
constructor, which takes 3 arguments, my metaclass should be callable without any arguments:
Cls1 = MyMeta()
Cls2 = MyMeta()
...
For this purpose I defined a custom __new__
method with no parameters:
class MyMeta(type):
def __new__(cls):
return super().__new__(cls, 'MyCls', (), {})
But the problem is that python automatically calls the __init__
method with the same arguments as the __new__
method, so trying to call MyMeta()
ends up throwing an exception:
TypeError: type.__init__() takes 1 or 3 arguments
Which makes sense, since type
can be called with 1 or 3 arguments. But what's the correct way to fix this? I see 3 (4?) options:
__init__
method to my metaclass, but since I'm not sure if type.__init__
does anything important, this might not be a good idea.__init__
method that calls super().__init__(cls.__name__, cls.__bases__, vars(cls))
.__call__
method, rather than messing with __new__
and __init__
.So my question is: Are the 3 solutions I listed correct or are there any subtle bugs hidden in them? Which solution is best (i.e. the most correct)?
An interface deviating from the parent signature is a questionable design in regular classes too. You don't need the extra complexity of metaclasses to get into this kind of mess - you can cause the same new/init jumble by subclassing a datetime
or whatever.
I want to have a metaclass and an easy way to create instances of that metaclass.
The usual pattern in Python is to write a factory using a from_something
classmethod. To take the example of creating datetime instances from a different init signature, there is for example datetime.fromtimestamp
, but you have many other examples too (dict.fromkeys
, int.from_bytes
, bytes.fromhex
...)
There is nothing specific to metaclasses here, so use the same pattern:
class MyMeta(type):
@classmethod
def from_no_args(cls, name=None):
if name is None:
name = cls.__name__ + 'Instance'
return cls(name, (), {})
Usage:
>>> class A(metaclass=MyMeta):
... pass
...
>>> B = MyMeta.from_no_args()
>>> C = MyMeta.from_no_args(name='C')
>>> A.__name__
'A'
>>> B.__name__
'MyMetaInstance'
>>> C.__name__
'C'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With