The Construction Method of Classes in Python the Wonderful Use of __New__
- 2021-12-09 09:40:03
- OfStack
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:
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