ホームページ  >  記事  >  バックエンド開発  >  Python の __new__ メソッドの役割を詳しく説明した記事

Python の __new__ メソッドの役割を詳しく説明した記事

WBOY
WBOY転載
2023-04-12 22:46:051901ブラウズ

Python の __new__ メソッドの役割を詳しく説明した記事

まえがき

Python のクラス構築メソッドの __new__ メソッドの機能は何ですか?

Python クラスの一部のメソッド名と属性名の前後には __ の二重アンダースコアが付いています。このようなメソッドと属性は通常、Python の特別なメソッドと特別な属性に属します。これらのメソッドをオーバーライドするか、これらのメソッドを直接呼び出して、特別な関数を実装します。今日は、__new__ という構築方法を実際のプログラムに適用するシナリオについて話しましょう。

一般的な初期化 __init__ メソッドを知っているので、それを書き換えて必要な初期化ロジックを実装できます。最近、実際のビジネス開発プロセスにおいて、データリソースのロードやキャッシュの仕組みの実装などで、ある種の問題に遭遇することがありますが、その中で__init__()と__new__というコンストラクタであるマジックメソッドの構築方法が使われています。オブジェクトの適切な使用により、プログラムのパフォーマンスが効果的に向上します。

誰もがコードを深く理解し、自分のビジネス ニーズに基づいて柔軟に使用して、コードをより洗練できるようになることを願っています。

1. __new__ メソッドの紹介

次に、クラスの初期化プロセスにおける __ new__ メソッドの存在について、例を通して徐々に詳しく説明していきます。

1. データ読み込み解析クラス インスタンスの初期化

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

注:

1)、コードのインスタンス化クラス プロセス

通常は __init__() メソッドを使用します。クラスのインスタンス。コードでクラスがインスタンス化されるとき、実行される最初の呼び出しは __new__() メソッドです。定義されたクラスで __new__() メソッドが再定義されていない場合、Python はデフォルトで親を呼び出します。 ) クラスのメソッドを使用してインスタンスを構築します。新しいメソッドは、最初にスペースを作成し、次にインスタンス化されたオブジェクトを毎回作成し、次にインスタンス化されたオブジェクトを再度作成するときに、開いたスペースを使用してインスタンス化されたオブジェクトを保存します。次に、新しいメソッドを使用して、インスタンス化されたオブジェクトを保存するスペースを開きます。オブジェクトを継承するクラスのみがこのメソッドを持つことに注意してください。

2)、メモリ アドレスとオブジェクトは相互に変換できます。

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

print(id(a)) および print(id(b)) はメモリ アドレス (10 進数) を出力します。 print(a) と print(b) はクラスの名前とオブジェクトのアドレスを返しますが、それらは同じではありません。クラスがインスタンス化されるたびに、異なるオブジェクト アドレスが作成されて割り当てられるため、コードのインスタンス化プロセス中に返されるクラス オブジェクトのアドレス参照も異なります。

2. データ読み込みの初期化と、クラス インスタンスを解析するための新しいメソッドのオーバーライド

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))

注:

1)、__init__() メソッドと __new__( )メソッド

__new__() メソッドは、インスタンスの作成に使用されます。このメソッドは、クラスがインスタンス化される前に最初に呼び出されます。これは、クラス メソッドであり、静的メソッドです。 __init__() メソッドは、インスタンスを初期化するために使用されます。このメソッドは、インスタンス オブジェクトが作成された後に呼び出されます。これは、インスタンス オブジェクトのメソッドであり、クラス インスタンス オブジェクトのいくつかの初期値を設定するために使用されます。

__init__() メソッドと __new__() メソッドがクラス内に同時に存在する場合、__new__() メソッドが最初に呼び出され、次に __init__() メソッドが呼び出されます。 __new__() メソッドは、インスタンス作成の最初のステップです。実行後、作成されたクラスのインスタンスを返す必要があります。返さないと、エラーが報告され、__init__() メソッドは実行できません。このうち、__init__() メソッドは情報を返しません。

2)、__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() と object.__new__(cls) は両方とも親クラスの新しいメソッドと親の新しいメソッドを呼び出しますclass は関数に戻ることによってのみ空間を開くことができるため、return を追加する必要があります。コードの実行順序は、最初に新しいメソッドを実行し、次に init メソッドを実行し、最後に他のメソッドを実行します。

2. シングルトン パターン

シングルトン パターンの元の定義は、「デザイン パターン」に記載されており、「クラスのインスタンスが 1 つだけであることを確認し、それにアクセスするためのグローバル アクセス ポイントを提供する」というものでした。

シングルトンは主に、システム ログの出力やオペレーティング システムのタスク マネージャーなど、グローバルに 1 つのインスタンスのみにアクセスできるようにする必要がある場合に使用されます。

1. 新しい方法を使用してシングルトン モードを実装するにはどうすればよいですか?

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

注:

1) シングルトン モードには常にスペースが 1 つだけあり、このスペースは常に再利用されます。

最初にクラスのプライベート属性 _instance を定義して、最初に作成されたオブジェクトの参照を記録します。cls._instance が None の場合、クラスがまだインスタンス化されていないことを意味し、クラスをインスタンス化してインスタンス オブジェクトを返します。

次のデータ テストにより、print(obj.name, obj.data) の最終的な印刷出力は A12 であることがわかります。「A11」が初めて印刷されるとき、属性は空です。 if文を実行するとスペースが空きますので、この属性を保存し、2回目以降スペースが開いている状態でelse文を実行し、「A12」を直接元のスペースに戻し、前回の表紙データを上書きします。

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)、シングルトンモードの別の実装方法

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. 初期化メソッドを一度だけ実行するようにクラスを制御するにはどうすればよいですか?

上記はシングルトン モードのオブジェクト空間の再利用を実現しますが、システム リソースを無駄にする頻繁なリクエスト (データベース接続リクエスト データなど) を避けるために、初期化プロセスを 1 回だけロードしたい場合があります。

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

注:

1). 初期化プロセスは、シングルトン モードでは 1 回だけロードされます。

这时候我们在类空间中再添加一个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为某一批需要通用功能的类添加方法,若有合适实例场景后续分享。

以上がPython の __new__ メソッドの役割を詳しく説明した記事の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事は51cto.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。