The Construction Method of Classes in Python the Wonderful Use of __New__

  • 2021-12-09 09:40:03
  • OfStack

Catalog 1, Overview 2. Difference between __new__ and __init__ 3. Application 1: Change the built-in immutable type 4. Application 2: Implement a singleton 5. Application 3: Client cache 6. Application 4: Different decryption methods for different files

1. Overview

python In the class, all the methods wrapped in double underscore __ are called magic methods. Magic methods can be automatically executed after some events of the class or object are issued, which makes the class have magical magic, such as common constructors __new__ The initialization method __init__ Destructional method __del__ Let's talk today. __new__ The wonderful use of, mainly share the following points:

Differences between __new__ and __init__ Application 1: Change the built-in immutable type Application 2: Implement a singleton Application 3: Client Caching Application 4: Different decryption methods for different files Application 5: Metaclasses

2. Differences between __new__ and __init__

The invocation timing is different: new Is the way to actually create an instance, init Used for initialization of an instance, new Prior to init Run. The return value is different, new Returns an instance of 1 class, while init No information is returned. new Yes class The method, and init Is a method of an object.

Sample code:


class A: 
    def __new__(cls, *args, **kwargs): 
        print("new", cls, args, kwargs) 
        return super().__new__(cls) 
 
    def __init__(self, *args, **kwargs): 
        print("init", self, args, kwargs) 
 
 
def how_object_construction_works(): 
    x = A(1, 2, 3, x=4) 
    print(x)     
    print("===================") 
    x = A.__new__(A, 1, 2, 3, x=4) 
    if isinstance(x, A): 
        type(x).__init__(x, 1, 2, 3, x=4) 
    print(x) 
 
if __name__ == "__main__": 
    how_object_construction_works() 


The above code defines a class A, which executes new before init when calling A (1, 2, 3, x=4), which is equivalent to:


x = A.__new__(A, 1, 2, 3, x=4) 
if isinstance(x, A): 
    type(x).__init__(x, 1, 2, 3, x=4) 


The code runs as follows:


new <class '__main__.A'> (1, 2, 3) {'x': 4} 
init <__main__.A object at 0x7fccaec97610> (1, 2, 3) {'x': 4} 
<__main__.A object at 0x7fccaec97610> 
=================== 
new <class '__main__.A'> (1, 2, 3) {'x': 4} 
init <__main__.A object at 0x7fccaec97310> (1, 2, 3) {'x': 4} 
<__main__.A object at 0x7fccaec97310> 


new The main function of is to let programmers customize the creation behavior of classes. The following are its main application scenarios:

3. Application 1: Change the built-in immutable type

We know that tuples are immutable types, but we inherit tuple , and then you can use the new The element of its tuple is modified because new Before returning, the tuple is not a tuple, which cannot be realized in init function. For example, to implement a capitalized tuple, the code is as follows:


class UppercaseTuple(tuple): 
    def __new__(cls, iterable): 
        upper_iterable = (s.upper() for s in iterable) 
        return super().__new__(cls, upper_iterable) 
 
    #  The following code will report an error and cannot be modified during initialization  
    # def __init__(self, iterable): 
    #     print(f'init {iterable}') 
    #     for i, arg in enumerate(iterable): 
    #         self[i] = arg.upper() 
 
if __name__ == '__main__': 
    print("UPPERCASE TUPLE EXAMPLE") 
    print(UppercaseTuple(["hello", "world"])) 
 
# UPPERCASE TUPLE EXAMPLE 
# ('HELLO', 'WORLD') 

4. Application 2: Implement a singleton


class Singleton: 
    _instance = None 
 
    def __new__(cls, *args, **kwargs): 
        if cls._instance is None: 
            cls._instance = super().__new__(cls, *args, **kwargs) 
        return cls._instance 
 
 
if __name__ == "__main__": 
 
    print("SINGLETON EXAMPLE") 
    x = Singleton() 
    y = Singleton() 
    print(f"{x is y=}") 
# SINGLETON EXAMPLE 
# x is y=True 

5. Application 3: Client Caching

When the cost of creating a client is relatively high, such as reading files or databases, the following methods can be adopted. The same client belongs to the same instance, which saves the cost of creating objects. This is essentially a multi-instance pattern.


class Client: 
    _loaded = {} 
    _db_file = "file.db" 
 
    def __new__(cls, client_id): 
        if (client := cls._loaded.get(client_id)) is not None: 
            print(f"returning existing client {client_id} from cache") 
            return client 
        client = super().__new__(cls) 
        cls._loaded[client_id] = client 
        client._init_from_file(client_id, cls._db_file) 
        return client 
 
    def _init_from_file(self, client_id, file): 
        # lookup client in file and read properties 
        print(f"reading client {client_id} data from file, db, etc.") 
        name = ... 
        email = ... 
        self.name = name 
        self.email = email 
        self.id = client_id 
 
 
if __name__ == '__main__': 
    print("CLIENT CACHE EXAMPLE") 
    x = Client(0) 
    y = Client(0) 
    print(f"{x is y=}") 
    z = Client(1) 
# CLIENT CACHE EXAMPLE 
# reading client 0 data from file, db, etc. 
# returning existing client 0 from cache 
# x is y=True 
# reading client 1 data from file, db, etc. 

6. Application 4: Different decryption methods for different files

First, create three files in the directory where the script is located: plaintext_hello.txt、rot13_hello.txt、otp_hello.txt, The program will choose different decryption algorithms according to different files


import codecs 
import itertools 
 
 
class EncryptedFile: 
    _registry = {}  # 'rot13' -> ROT13Text 
 
    def __init_subclass__(cls, prefix, **kwargs): 
        super().__init_subclass__(**kwargs) 
        cls._registry[prefix] = cls 
 
    def __new__(cls, path: str, key=None): 
        prefix, sep, suffix = path.partition(":///") 
        if sep: 
            file = suffix 
        else: 
            file = prefix 
            prefix = "file" 
        subclass = cls._registry[prefix] 
        obj = object.__new__(subclass) 
        obj.file = file 
        obj.key = key 
        return obj 
 
    def read(self) -> str: 
        raise NotImplementedError 
 
 
class Plaintext(EncryptedFile, prefix="file"): 
    def read(self): 
        with open(self.file, "r") as f: 
            return f.read() 
 
 
class ROT13Text(EncryptedFile, prefix="rot13"): 
    def read(self): 
        with open(self.file, "r") as f: 
            text = f.read() 
        return codecs.decode(text, "rot_13") 
 
 
class OneTimePadXorText(EncryptedFile, prefix="otp"): 
    def __init__(self, path, key): 
        if isinstance(self.key, str): 
            self.key = self.key.encode() 
 
    def xor_bytes_with_key(self, b: bytes) -> bytes: 
        return bytes(b1 ^ b2 for b1, b2 in zip(b, itertools.cycle(self.key))) 
 
    def read(self): 
        with open(self.file, "rb") as f: 
            btext = f.read() 
        text = self.xor_bytes_with_key(btext).decode() 
        return text 
 
 
if __name__ == "__main__": 
    print("ENCRYPTED FILE EXAMPLE") 
    print(EncryptedFile("plaintext_hello.txt").read()) 
    print(EncryptedFile("rot13:///rot13_hello.txt").read()) 
    print(EncryptedFile("otp:///otp_hello.txt", key="1234").read()) 
# ENCRYPTED FILE EXAMPLE 
# plaintext_hello.txt 
# ebg13_uryyb.gkg 
# ^FCkYW_X^GLE 


Related articles: