Reputation: 25
I am having some trouble understanding how the .new magic method creates classes. Below is an example from an API I am using that uses metaclasses to dynamically create objects from information in a database.
class Metaclass(type):
def __new__(cls, name, bases, dict_):
print(f' cls - {cls}')
print(f' name - {name}')
print(f' bases - {bases}')
return super(Metaclass, cls).__new__(cls, name, bases, dict_)
class MyObject(object, metaclass=Metaclass):
pass
class PrimaryObject(MyObject):
pass
class SecondaryObject(MyObject):
pass
test = PrimaryObject()
Running this code causes a PrimaryObject to be instantiated, which inherits from MyObject, which in turn inherits from the metaclass. The new magic method in the metaclass is then called, which iterates three times through all of the objects that inherit from the metaclass, even ones which weren't called:
<class '__main__.Metaclass'>
MyObject
(<class 'object'>,)
<class '__main__.Metaclass'>
PrimaryObject
(<class '__main__.MyObject'>,)
<class '__main__.Metaclass'>
SecondaryObject
(<class '__main__.MyObject'>,)
<__main__.PrimaryObject object at 0x000002214A6786D0>
How is the new method receiving these arguments, as nothing is passed to it? Are they parsed from the object that is calling it? And if so, why and how is it iterating through other objects that inherit from the object but weren't instatiated (SecondaryObject)?
Thanks
Upvotes: 0
Views: 47
Reputation: 110271
"...which in turn inherits from the metaclass..."
THis is the wrong part in your assumptions. None of those classes "inherit" from the metaclass.
The metaclass is used to build them - the classes themselves - just once
for each class. That is when __new__
is called: when Python executes the class
statement (along with the class body).
Instantiating PrimaryObject
won't call the metaclass __new__
anymore - just add another print statement there, before the test = ...
line and you will see this.
however, if you want a method on the metaclass to be called when you create an instance of the classes created with the metaclass, that is the __call__
method, not __new__
. When you call super().__call__(...)
inside your metaclass call, it will run type.__call__
, which in turn is what runs the class __new__
and __init__
methods, creating a new instance.
print("defining the metaclass")
class Metaclass(type):
def __new__(cls, name, bases, dict_):
print(f' cls - {cls}')
print(f' name - {name}')
print(f' bases - {bases}')
return super(Metaclass, cls).__new__(cls, name, bases, dict_)
def __call__(cls, *args, **kw):
print(f"Creating a new instance of {cls.__name__}")
return super().__call__(*args, **kw)
print("Creating the classes that use the metaclass")
class MyObject(object, metaclass=Metaclass):
pass
class PrimaryObject(MyObject):
pass
class SecondaryObject(MyObject):
pass
print("Creating a class instance")
test = PrimaryObject()
Upvotes: 1
Reputation: 531075
The metaclass is the type of a class. Given your code, compare
>>> type(MyObject)
<class '__main__.Metaclass'>
>>> type(int)
<class 'type'>
Ultimately, all metaclasses inherit from type
.
A class
statement is a construct used to make an implicit call to some metaclass.
# A = type('A', (), {})
class A:
pass
# MyObject = Metaclass('MyObject', (), {})
class MyObject(metaclass=Metaclass):
pass
How the metaclass is chosen is outlined in the Python language reference.
Like all types, a call to the type itself invokes a call to its __new__
method, so
MyObject = Metaclass('MyObject', (), {})
is roughly equivalent to
MyObject = Metaclass.__new__(Metaclass, 'MyObject', (), {})
if isinstance(MyObject, Metaclass):
MyObject.__init__()
Upvotes: 0