Maison  >  Article  >  développement back-end  >  Un article expliquant en détail le rôle de la méthode __new__ en Python

Un article expliquant en détail le rôle de la méthode __new__ en Python

WBOY
WBOYavant
2023-04-12 22:46:051900parcourir

Un article expliquant en détail le rôle de la méthode __new__ en Python

Préface

Quelle est la fonction de la méthode __new__ du constructeur de classe en Python ?

Certains noms de méthodes et noms d'attributs dans les classes Python sont précédés et suivis de __ doubles traits de soulignement. Ces méthodes et attributs sont généralement des méthodes et attributs spéciaux de Python. Implémentez des fonctions spéciales en remplaçant ces méthodes ou en appelant ces méthodes directement. Parlons aujourd'hui des scénarios d'application de la méthode de construction __new__ dans des programmes réels.

Nous connaissons la méthode d'initialisation courante __init__ et pouvons la réécrire pour implémenter la logique d'initialisation que nous souhaitons. Récemment, nous avons rencontré un type de problème dans le processus de développement commercial réel, tel que la mise en œuvre du mécanisme de chargement et de mise en cache des ressources de données. Parmi eux, __init__() et __new__ sont les constructeurs. de l'objet. Une utilisation raisonnable améliorera efficacement les performances du programme.

J'espère que chacun pourra le comprendre en profondeur et l'utiliser de manière flexible en fonction des besoins de son entreprise afin de rendre le code plus élégant.

1. Introduction à la méthode __new__

Ensuite, nous expliquerons en détail comment la méthode __ new __ existe dans le processus d'initialisation de classe à travers des exemples !

1. Initialisation du chargement des données + analyse des instances de classe

class Solution(object):
 def __init__(self, name=None,data=None):
 self.name = name
 self.data = data
 #初始化加载数据
 self.xml_load(self.data)
 def xml_load(self,data):
 print("初始化init",data)
 def Parser(self):
 print("解析完成finish",self.name)
a = Solution(name="A111",data=10)
a.Parser()
b = Solution(name="A112",data=20)
b.Parser()
# print(a)与 print(b)返回了类的名称和对象的地址
print(a)
print(b)
# 可以使用内置函数id()查看python对象的内存地址
print(id(a))
print(id(b))
初始化init 10
解析完成finish A111
初始化init 20
解析完成finish A112
<__main__.Solution object at 0x0000024A3AF28D48>
<__main__.Solution object at 0x0000024A3B055C48>
2517839809864
2517841042504

Remarque :

1). Processus de classe d'instanciation de code

Utilisez généralement la méthode __init__() pour initialiser une instance d'une classe lorsqu'une classe est instanciée dans le code. , le premier appel exécuté est la méthode __new__() Lorsque la méthode __new__() n'est pas redéfinie dans la classe définie, Python appellera par défaut la méthode __new__() de la classe parent pour construire l'instance. créez un espace, puis créez un objet instancié à chaque fois, puis utilisez l'espace ouvert pour stocker l'objet instancié lors de la création à nouveau d'un objet instancié, utilisez la nouvelle méthode pour ouvrir un espace pour stocker l'objet instancié ; Notez que seules les classes qui héritent d'un objet ont cette méthode.

2), les adresses mémoire et les objets peuvent être convertis les uns aux autres

#通过_ctypes的api进行对内存地址的对象
import _ctypes
obj = _ctypes.PyObj_FromPtr(id(a))
#打印出来通过内存地址寻找到的对象
print(obj)

print(id(a)) et print(id(b)) imprime l'adresse mémoire (décimale), print(a) et print( b ) Le nom de la classe et l'adresse de l'objet sont renvoyés, mais ils ne sont pas identiques. Chaque fois qu'une classe est instanciée, une adresse d'objet différente est créée et allouée. Par conséquent, la référence d'adresse de l'objet de classe renvoyée lors du processus d'instanciation du code est également différente.

2. Initialisez le chargement des données et réécrivez une nouvelle méthode + analysez l'instance de classe

class Solution:
 """
 注:new方法是为实例化对象创建空间的方法,现在new方法被改写,没有将实例化对象引用返回给python的解释器
 无法为实例化对象创建空间存储,所以运行代码会报错。也没有完成初始化操作。
 """
 def __new__(cls, *args, **kwargs):
 print("对象创建空间")
 cls.instance = super().__new__(cls)
 print(cls.instance)
 # return cls.instance #若未返回实例对象引用,实例化方法将报错:AttributeError: 'NoneType' object has no attribute 'Parser'
 def __init__(self,name,data):
 self.name = name
 self.data = data
 self.xml_load(self.data)
 def xml_load(self,data):
 print("初始化init", data)
 def Parser(self):
 print("解析完成finish",self.data)
a = Solution("A111",10)
a.Parser()
print(id(a))

Remarque :

1), la différence entre la méthode __init__() et la méthode __new__()

__new__() est utilisée pour créer des instances et une classe. instanciation Elle sera appelée avant. C'est une méthode de classe et une méthode statique. La méthode __init__() est utilisée pour initialiser l'instance. Cette méthode est appelée après la création de l'objet instance et est utilisée pour définir certaines valeurs initiales de l'objet instance de classe.

Si la méthode __init__() et la méthode __new__() apparaissent dans la classe en même temps, la méthode __new__() sera appelée en premier puis la méthode __init__(). La méthode __new__() est la première étape de la création d'une instance. Après l'exécution, l'instance de la classe créée doit être renvoyée. Sinon, une erreur sera signalée et la méthode __init__() ne pourra pas être exécutée. Parmi eux, la méthode __init__() ne renverra aucune information.

2), la réécriture de la méthode __new__()

def __new__(cls, *args, **kwargs):
 print(cls)# cls 代表的是Solution这个类本身<class'__ main __.Solution'>
 cls.instance = super().__new__(cls)# object().__ new __()
 print(cls.instance)
 return cls.instance

super() et object.__new__(cls) appellent toutes deux la nouvelle méthode de la classe parent. La nouvelle méthode de la classe parent doit être renvoyée à la fonction pour s'ouvrir. de l'espace. Par conséquent, le retour doit être ajouté. L'ordre d'exécution du code est le suivant : exécutez d'abord la nouvelle méthode, puis exécutez la méthode init et enfin les autres méthodes.

2. Modèle Singleton

La définition originale du modèle singleton est apparue dans "Design Pattern" : "Assurez-vous qu'une classe n'a qu'une seule instance et fournissez un point d'accès global pour y accéder."

L'utilisation principale du singleton est In situations où il est nécessaire de garantir qu'une seule instance du monde est accessible, comme la sortie des journaux système, le gestionnaire de tâches du système d'exploitation, etc.

1. Comment implémenter le mode singleton en utilisant une nouvelle méthode ?

class Solution:
 # 1、记录第一个被创建对象的引用,代表着类的私有属性
 _instance = None # 静态变量 存储在类的命名空间里的
 def __init__(self,name,data):
 self.name = name
 self.data = data
 self.xml_load(self.data)
 def __new__(cls, *args, **kwargs):
 # 2.判断该类的属性是否为空;对第一个对象没有被创建,我们应该调用父类的方法,为第一个对象分配空间
 if cls._instance == None:
 # 3.把类属性中保存的对象引用返回给python的解释器
 cls._instance = object.__new__(cls)# 3
 return cls._instance
 # 如果cls._instance不为None,直接返回已经实例化了的实例对象
 else:
 return cls._instance# 必须把地址返回给new方法,让它有存储空间
 def xml_load(self,data):
 print("初始化init",self.name,data)
 def Parser(self):
 print("解析完成finish",self.name)
a = Solution("A11",10)#第一次开辟一个对象空间地址,后面创建都是在该地址上进行的
a.Parser()
b = Solution("A12",20)#b把a覆盖掉
b.Parser()
print(id(a))
print(id(b))
# 内存地址,而且它们的内存地址都是一样的
print(a.name)
print(b.name)
初始化init A11 10
解析完成finish A11
初始化init A12 10
解析完成finish A12
2465140199816
2465140199816
A12
A12

Remarque :

1). Le mode singleton n'a toujours qu'un seul espace, et cet espace est toujours réutilisé.

Définissez d'abord l'attribut privé _instance d'une classe pour enregistrer la référence du premier objet créé. Si cls._instance vaut None, cela signifie que la classe n'a pas encore été instanciée, instanciez la classe et renvoyez l'objet instance.

Grâce au test de données suivant, on peut voir que l'impression finale de print(obj.name, obj.data) est A12 Lorsque "A11" est imprimé pour la première fois, l'attribut est vide. ouvre un espace pour stocker cet attribut ; L'espace a été ouvert depuis le deuxième accès. Exécutez l'instruction else et renvoyez directement "A12" à l'espace d'origine, en écrasant les données de couverture précédentes.

def task(id,data):
 obj = Solution("{0}".format(id), "{0}".format(data))
 print(obj.name, obj.data)
import threading
ID=["A11","A12","A13","A14","A12"]
DATA=[10,20,30,40,20]
for i in range(5):
 t = threading.Thread(target=task(ID[i],DATA[i]), args=[i, ])
 t.start()
<__main__.Solution object at 0x00000221B2129148>
初始化init A11 10
A11 10
初始化init A12 20
A12 20
初始化init A13 30
A13 30
初始化init A14 40
A14 40
初始化init A12 20
A12 20

2), une autre méthode d'implémentation du mode singleton

def __new__(cls,*args,**kwargs):
 # hasattr查询目标并判断有没有,not1==1返回的是False
 # if语句后面的
 # not 条件整体为True时,执行cls.instance = object....代码
 # if语句后面的
 # not 条件整体为False时,执行return代码
 if not hasattr(cls,"instance"): # hasattr查、判断的作用
 cls.instance = object.__new__(cls)
 return cls.instance

2 Comment contrôler la classe pour n'exécuter la méthode d'initialisation qu'une seule fois ?

Ce qui précède réalise la réutilisation de l'espace objet en mode singleton, mais parfois nous souhaitons charger le processus d'initialisation une seule fois pour éviter les requêtes fréquentes qui gaspillent les ressources du système (telles que les données de demande de connexion à la base de données).

class Solution:
 #定义类变量
 # 记录第一个被创建对象的引用,代表着类的私有属性
 _instance = None
 #记录是否执行过初始化动作
 init_flag = False
 def __init__(self,name,data):
 self.name = name
 self.data = data
 #使用类名调用类变量,不能直接访问。
 if Solution.init_flag:
 return
 self.xml_load(self.data)
 # 修改类属性的标记
 Solution.init_flag = True
 def __new__(cls, *args, **kwargs):
 # 判断该类的属性是否为空;对第一个对象没有被创建,我们应该调用父类的方法,为第一个对象分配空间
if cls._instance == None:
 # 把类属性中保存的对象引用返回给python的解释器
 cls._instance = object.__new__(cls)
 return cls._instance
 #如果cls._instance不为None,直接返回已经实例化了的实例对象
 else:
return cls._instance
 def xml_load(self,data):
 print("初始化init",self.name,data)
 def Parser(self):
 print("解析完成finish",self.name)
a = Solution("A11",10)#第一次实例化对象地址,后面创建都是在该地址上进行的
a.Parser()
b = Solution("A12",20)#b把a覆盖掉
b.Parser()
print(id(a))
print(id(b))
print(a.name)
print(b.name)
初始化init A11 10
解析完成finish A11
解析完成finish A12
2280855720328
2280855720328
A12
A12

Remarque :

1), ne chargez le processus d'initialisation qu'une seule fois en mode singleton.

这时候我们在类空间中再添加一个init_flag属性来记录是否已经执行过初始化操作即可实现加载一次初始化过程。从以上两次实例化过程结果来看,对象引用地址不变,结果被最后一次实例化数据覆盖且初始化init只被打印一次。

2)、单例模式下一次资源加载注意点

单例模式下控制类仅进行一次初始化过程适用于资源一次性加载进缓存的过程,对于多进程应用可采用多例模式实现。

三、多例模式

多个实例对象空间引用地址完全独立,从而保持避免不同请求资源不被占用。将同一个对象请求归为同一个实例。

class Solution:
 ##定义类实例化对象字典,即不同的实例对象对应不同的对象空间地址引用
 _loaded = {}
 def __init__(self,name,data):
 self.name = name
 self.data = data
 self.xml_load(self.data)
 def __new__(cls, name,*args):
 if cls._loaded.get(name) is not None:
 client = cls._loaded.get(name)
 print(f"已经存在访问对象 {name}")
 print(client)
 return client
 # 把类属性中保存的对象引用返回给python的解释器
 print(f"正在创建访问对象 {name}")
 client = super().__new__(cls)
 # 为该类实例name添加一个空间对象地址引用
 print(client)
 cls._loaded[name] = client
 return client
 def xml_load(self,data):
 print("初始化init",self.name,data)
 def Parser(self):
 print("解析完成finish",self.name)
if __name__ == '__main__':
 print("多例模式实例")
 a = Solution("A11",10)
 a.Parser()
 b = Solution("A11",10)
 b.Parser()
 c = Solution("A12", 20)
 c.Parser()
 print(f"{a is b}")
 print(a.name)
 print(b.name)
 print(c.name)

注:

1)、多例模式始终具有多个空间,不同空间完全独立。

我们在类空间中定义类实例化对象字典,即建立不同的实例对象和对象空间地址引用键值对,从而实现多例模式。通过类字典判断实例对象是否创建,节省创建的成本。

2)、多例模式测试过程

当创建相同的实例对象name="A11"时,程序首先在实例池中搜索cls._loaded.get(name),若存在则直接返回已创建的实例对象空间。多例模式完美的实现了不同访问对象具体不同的实例化对象地址。

多例模式实例
正在创建访问对象 A11
<__main__.Solution object at 0x000001C105AA5EC8>
初始化init A11 10
解析完成finish A11
已经存在访问对象 A11
<__main__.Solution object at 0x000001C105AA5EC8>
初始化init A11 10
解析完成finish A11
正在创建访问对象 A12
<__main__.Solution object at 0x000001C105AA5F88>
初始化init A12 20
解析完成finish A12
True
A11
A11
A12

3)、多例模式下缓冲机制的实现

进一步优化多例模式初始化过程,比如读取文件或者数据库时仅进行一次初始化加载。

class Solution:
 ##定义类实例化对象字典,即不同的实例对象对应不同的对象空间地址引用
 _loaded = {}
 def __new__(cls, name,data,*args):
 if cls._loaded.get(name) is not None:
 client = cls._loaded.get(name)
 print(f"已经存在访问对象 {name}")
 print(client)
 return client
 print(f"正在创建访问对象 {name}")
 # 把类属性中保存的对象引用返回给python的解释器
 client = super().__new__(cls)
 print(client)
 # 为该类实例name添加一个空间对象地址引用
 cls._loaded[name] = client
 client._init_db(name,data)
 return client
 def _init_db(self,name,data):
 self.name = name
 self.data = data
 self.xml_load(self.data)
 def xml_load(self,data):
 print("初始化init",self.name,data)
 def Parser(self):
 print("解析完成finish",self.name)
if __name__ == '__main__':
 print("多例模式实例-缓存")
 a = Solution("A11",10)
 a.Parser()
 b = Solution("A11",10)
 b.Parser()
 c = Solution("A12", 20)
 c.Parser()
 print(f"{a is b}")
 print(a.name)
 print(b.name)
 print(c.name)
多例模式实例
正在创建访问对象 A11
<__main__.Solution object at 0x0000024198989148>
初始化init A11 10
解析完成finish A11
已经存在访问对象 A11
<__main__.Solution object at 0x0000024198989148>
解析完成finish A11
正在创建访问对象 A12
<__main__.Solution object at 0x00000241989891C8>
初始化init A12 20
解析完成finish A12
True
A11
A11
A12

注:多例模式下多个实例化对象均只进行一次初始化过程。

重写__new__方法中每个实例对象创建后绑定初始化_init_db()方法执行一次,后面遇到同一个实例对象将不会发生什么,直接返回已创建的实例对象。从测试结果来看,创建相同的实例对象name="A11"时,第二次将略过初始化数据加载过程,很好的实现了缓存机制。

__new__方法总结

本文结合项目背景详细介绍了__new__方法实现单例模式和多例模式以及缓存机制的实现!

1、__new__ 方法是在类创建实例的时候自动调用的。

2、 实例是通过类里面的 __ new __ 方法是在类创建出来的。

3、 先调用__new__ 方法创建实例,再调用 __ init __方法初始化实例。

4、 __new__ 方法,后面的括号里面的cls代表的是类本身。

5、__new__ 方法,判断类属性为空就去开辟空间,否则复用原来的地址。

更多的特殊方法比如1、自我描述方法:__repr__2、析构方法:__del__ 3、列出对象所有属性(包括方法)名:__dir__4、__dict__属性:查看对象内部所有属性名和属性值组成的字典5、__getattr____setattr__等。

当然还有metaclass类的__new__方法,可以动态修改程序中的一批类,这个功能在开发一些基础性的框架时非常有用,可以使用metaclass为某一批需要通用功能的类添加方法,若有合适实例场景后续分享。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer