This article originated from personal public account: TechFlow, original is not easy, for attention


In this article, the 17th in the Python series, we’ll talk about a new Python default function __new__.

In the last article we looked at how to use the type function to dynamically create classes in Python. In addition to what type can do for you, there is another use called metaclass. This article was supposed to continue with metaclass and explain the use of metaclass. But metaclass uses a new default function, __new__, which may be unfamiliar, so before we look at metaclass, let’s look at how __new__ is used.

True false constructor

If you are interviewing for a Python engineer position and the interviewer asks you, what is the constructor of a class in Python?

You don’t think, of course, __init__! If you answer that, you probably won’t get the offer. __init__ is not a constructor in Python, __new__ is. Isn’t that a little mundshide? Don’t we always use the __init__ method as a constructor? If __new__ is the constructor, then why do we never use it when creating classes?

Don’t worry. We’ll take our time. First let’s review the use of __init__. Let’s write some random code:

class Student:

    def __init__(self, name, gender):

        self.name = name

        self.gender = gender

Copy the code

That’s how we use it all the time, right? No problem. But instead, how do we implement the Singleton design pattern in Python? How to implement the factory?

Starting with this problem, you can see that it is impossible to do this using only the __init__ function, because __init__ is not a constructor, it is just an initializer. That is, our instance is already created before __init__ is called, and __init__ simply assigns some value to the instance. If we compare the process of creating an instance to making a cake, the __init__ method doesn’t bake the cake, it just decorates it. So, obviously, a cake must be baked before it can be embellished, so the baking function is __new__.

__new__ function

Let’s look at the __new__ function definition. When using Python object orientation, we usually do not refactor this function. Instead, we use the default constructor provided by Python.

def __new__(cls, *args, **kwargs):

    return super().__new__(cls, *args, **kwargs)

Copy the code

As you can see from the code, the function basically does nothing but call the parent constructor. Hidden here is the logic for creating classes in Python, which are created level by level based on inheritance. When we create an instance, we call __new__ to create the instance, and then call __init__ to initialize the instance. We can do a simple experiment:

class Test:

    def __new__(cls):

        print('__new__')

        return object().__new__(cls)

    def __init__(self):

        print('__init__')

Copy the code

When we create the Test class, we know the order of calls in Python by the order in which they are printed.


The results are exactly what we thought they would be.

The singleton pattern

So what can we do if we override the __new__ function? It is generally used to do things __init__ cannot do, such as the singleton mode mentioned earlier, which is implemented by the __new__ function. Let’s make it simple:

class SingletonObject:

    def __new__(cls, *args, **kwargs):

        if not hasattr(SingletonObject, "_instance") :

            SingletonObject._instance = object.__new__(cls)

        return SingletonObject._instance

    

    def __init__(self):

        pass

Copy the code

Of course, if you’re using it in a concurrent scenario, you need to add a thread lock to prevent concurrency, but the logic is the same.

In addition to enabling some functionality, you can also control the creation of instances. __new__ is called first and __init__ is called later, so if None is returned when __new__ is called, the result is None. With this feature, we can control the creation of classes. For example, if a condition is set, the instance is created correctly only if the condition is met, otherwise None is returned.

For example, if we want to create a class that is an int but cannot have a value of 0, we can use __new__ to do this:

class NonZero(int):

    def __new__(cls, value):

        return super().__new__(cls, value) ifvalue ! =0 else None

Copy the code

So when we create it with a value of 0 we get None instead of an instance.

The factory pattern

Once we understand the nature of the __new__ function, we can use it flexibly. We can use it to implement many other design patterns, such as the well-known factory pattern.

The so-called factory pattern refers to the creation of different instances based on the values of parameters through an interface. The logic of the creation process is closed and the user does not have to relate to the implementation logic. Just like a factory can produce a variety of parts, users do not care about the production process, only need to tell the type of parts needed. Hence the name factory pattern.

Let’s say we create a series of game classes:

class Last_of_us:

    def play(self):

        print('the Last Of Us is really funny')

        

        

class Uncharted:

    def play(self):

        print('the Uncharted is really funny')

        



class PSGame:

    def play(self):

        print('PS has many games')

Copy the code

At this point we want to be able to use an interface to return different games based on different arguments. If __new__ is not passed, this logic can only be written as a function and cannot be implemented through object orientation. We can easily get instances of different classes with arguments by overloading __new__ :

class GameFactory:

    games = {'last_of_us': Last_Of_us, 'uncharted': Uncharted}

    def __new__(cls, name):

        if name in cls.games:

            return cls.games[name]()

        else:

            return PSGame()

        



uncharted = GameFactory('uncharted')

last_of_us = GameFactory('last_of_us')

Copy the code

conclusion

The use of the __new__ function should be understood. We do not use this function in general, but only in special situations. However, we learned it not only to implement design patterns, but more importantly to deepen our understanding of Python object orientation.

In addition, another scenario where __new__ is often used is metaclasses. So this article will lay the groundwork for other uses of metaclasses.

If you like this article, if you can, please click the following, give me a little encouragement, but also convenient access to more articles.