Josh Taylor
Josh Taylor

Reputation: 25

How is the .__new__function working in this example?

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

Answers (2)

jsbueno
jsbueno

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

chepner
chepner

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

Related Questions