python中__new__和__init__的實現(xiàn)
1 前言
在Python中,每個對象都有兩個特殊的方法:__new__和__init__。這兩個方法在對象的創(chuàng)建和初始化過程中起著重要的作用,但它們的功能和用法有所不同。
1.1 功能上的區(qū)別
__new__方法是Python中的一個魔術(shù)方法(Magic Method),用于創(chuàng)建一個新的對象實例。當我們在Python中創(chuàng)建一個對象時,實際上是調(diào)用了__new__方法來創(chuàng)建一個新的對象實例,然后再調(diào)用__init__方法來初始化這個對象。
__init__方法是Python中的一個普通方法,用于初始化一個已經(jīng)存在的對象。當我們使用__new__方法創(chuàng)建一個新的對象實例后(不可為None),就會調(diào)用這個對象的__init__方法來對對象進行初始化。
1.2 參數(shù)上的區(qū)別
__new__方法通常需要三個參數(shù):第一個參數(shù)是類(cls,即class),第二個參數(shù)是傳入的參數(shù)列表(args),即位置參數(shù);第三個參數(shù)也是傳入的參數(shù)列表(kwargs),即關(guān)鍵字參數(shù)。__new__方法的返回值是一個新的對象實例。
__init__方法通常需要1個或以上參數(shù):第一個參數(shù)是對象實例(self,也就是__new__方法返回的對象實例),后續(xù)可有可無的若干參數(shù)是傳入的參數(shù)列表(args),常用于設(shè)置實例化對象屬性。__init__方法的返回值是None。
1.3 調(diào)用時機上的區(qū)別
__new__方法在創(chuàng)建對象時被調(diào)用,它的調(diào)用時機是在__init__方法之前。__new__方法的返回值是一個新的對象實例,這個實例會被傳遞給__init__方法進行初始化。
__init__方法在對象被創(chuàng)建后被調(diào)用,它的調(diào)用時機是在__new__方法之后。__init__方法用于對已經(jīng)存在的對象進行初始化,它的參數(shù)列表通常包括傳遞給類的構(gòu)造函數(shù)的參數(shù)。
上述可知,沒有__new__方法,我們就無法創(chuàng)建新的對象實例;沒有__init__方法,我們就無法對已經(jīng)存在的對象進行初始化。兩者功能上雖有差別,但是是用于共同來創(chuàng)建和初始化一個對象的,所以兩者均很重要,下面則是具體使用的分析。
2 使用
2.1 簡單示例
class Clazz:
def __new__(cls, *args, **kwargs):
print("調(diào)用__new__")
print(f'cls:{cls}, args:{args}, kwargs: {kwargs}')
def __init__(self, name):
print("調(diào)用__init__")
print(f'self:{self}, name:{name}')
self.name = name
clazz = Clazz("xiaoxu")
執(zhí)行結(jié)果:
調(diào)用__new__
cls:<class '__main__.Clazz'>, args:('xiaoxu',), kwargs: {}
可以看到,上述代碼先執(zhí)行了__new__,但是并未執(zhí)行__init__方法,因為只有當我們使用__new__方法創(chuàng)建一個新的對象實例后,才會調(diào)用這個對象的__init__方法來對對象進行初始化。
__new__是一個內(nèi)置staticmethod,其首個參數(shù)必須是type類型,即要實例化的class本身,其負責為傳入的class type分配內(nèi)存、創(chuàng)建一個新實例并返回該實例,該返回值其實就是后續(xù)執(zhí)行__init__函數(shù)的入?yún)elf。
參考Python的源碼typeobject.c中定義的type_call函數(shù):
static PyObject *
type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
PyObject *obj;
if (type->tp_new == NULL) {
PyErr_Format(PyExc_TypeError,
"cannot create '%.100s' instances",
type->tp_name);
return NULL;
}
...
obj = type->tp_new(type, args, kwds); # 這里先執(zhí)行tp_new分配內(nèi)存、創(chuàng)建對象返回obj
obj = _Py_CheckFunctionResult((PyObject*)type, obj, NULL);
...
type = Py_TYPE(obj); # 這里獲取obj的class類型,并判定有tp_init則執(zhí)行該初始化函數(shù)
if (type->tp_init != NULL) {
int res = type->tp_init(obj, args, kwds);
if (res < 0) {
assert(PyErr_Occurred());
Py_DECREF(obj);
obj = NULL;
}
else {
assert(!PyErr_Occurred());
}
}
return obj;
}
執(zhí)行代碼class(*args, **kwargs) 時,其會先調(diào)用type_new函數(shù)(__new__方法)分配內(nèi)存創(chuàng)建實例并返回為obj,而后通過Py_TYPE(obj)獲取其具體type,再進一步檢查type->tp_init不為空則執(zhí)行該初始化函數(shù)(也就是__init__方法)。
若__new__方法返回為None,依然不會執(zhí)行__init__方法:
class Clazz:
def __new__(cls, *args, **kwargs):
print("調(diào)用__new__")
print(f'cls:{cls}, args:{args}, kwargs: {kwargs}')
return None
def __init__(self, name):
print("調(diào)用__init__")
print(f'self:{self}, name:{name}')
self.name = name
clazz = Clazz("xiaoxu")
print(clazz)
# 調(diào)用__new__
# cls:<class '__main__.Clazz'>, args:('xiaoxu',), kwargs: {}
# None
作如下修改:
class Clazz:
def __new__(cls, *args, **kwargs):
print("調(diào)用__new__")
print(f'cls:{cls}, args:{args}, kwargs: {kwargs}')
# x = super().__new__(cls) 等同寫法
x = super(Clazz, cls).__new__(cls)
print("self_first:", x)
return x
def __init__(self, name, age=99):
print("調(diào)用__init__")
print(f'self:{self}, name:{name}, age: {age}')
super(Clazz, self).__init__()
self.name = name
# Cannot return a value from __init__
# __init__是不需要返回值的
# return None
clazz = Clazz("xiaoxu", age=66)
print(clazz)
因為python中任何類都繼承于object 類,上述的super().__new__(cls),其實就是調(diào)用內(nèi)置的object.__new__()方法來創(chuàng)建對象實例。一般的形式有super(類名, cls).__new__(cls, … …)。
執(zhí)行結(jié)果如下:

結(jié)果也印證了上述提到的,__new__方法的返回值,就是后續(xù)執(zhí)行__init__函數(shù)的入?yún)elf。
小結(jié)說明:
1、繼承自object的新式類才有__new__。
2、__new__至少要有一個參數(shù)cls,代表當前類,此參數(shù)在實例化時由Python解釋器自動識別。
3、__new__必須要有返回值,返回實例化出來的實例,這點在自己實現(xiàn)__new__時要特別注意,可以return父類(通過super(當前類名, cls))__new__出來的實例,或者直接是object的__new__出來的實例。
4、__init__有一個參數(shù)self,就是這個__new__返回的實例,__init__在__new__的基礎(chǔ)上可以完成一些其它初始化的動作,__init__不需要返回值。
5、如果__new__創(chuàng)建的是當前類的實例,會自動調(diào)用__init__函數(shù),通過return語句里面調(diào)用的__new__函數(shù)的第一個參數(shù)是 cls 來保證是當前類實例,如果是其他類的類名,那么實際創(chuàng)建返回的就是其他類的實例,就不會調(diào)用當前類的__init__函數(shù),也不會調(diào)用其他類的__init__函數(shù)。
6、在定義子類時沒有重新定義__new__()時,Python默認是調(diào)用該類的直接父類的__new__()方法來構(gòu)造該類的實例,如果該類的父類也沒有重寫__new__(),那么將一直按此規(guī)矩追溯至object的__new__()方法,因為object是所有新式類的基類。
7、如果子類中重寫了__new__()方法,那么你可以自由選擇任意一個的其他的新式類(必定要是新式類,只有新式類必定都有__new__(),因為所有新式類都是object的后代,而經(jīng)典類則沒有__new__()方法)的__new__()方法來制造實例,包括這個新式類的所有前代類和后代類,只要它們不會造成遞歸死循環(huán)。不能調(diào)用自己的__new__,因為是遞歸死循環(huán)調(diào)用。
8、對于子類的__init__,其調(diào)用規(guī)則跟__new__是一致的,當然如果子類和父類的__init__函數(shù)都想調(diào)用,可以在子類的__init__函數(shù)中加入對父類__init__函數(shù)的調(diào)用。
2.2 __new__的作用
參考Python官方文檔,__new__方法主要是當你繼承一些不可變的class時(比如int, str, tuple), 提供給你一個自定義這些類的實例化過程的途徑,另外就是實現(xiàn)自定義的metaclass。
(1)根據(jù)int舉個栗子:
class PositiveInteger(int):
def __new__(cls, *args, **kwargs):
print("param:", args)
return super(PositiveInteger, cls).__new__(cls, abs(args[0]))
p = PositiveInteger(-5)
print(p)
執(zhí)行結(jié)果:

(2)再根據(jù)tuple舉個栗子:
__new__方法自定義要求保證實例創(chuàng)建、并且必須記得返回實例對象的一系列固定邏輯正確,而__init__方法相當簡單只需要設(shè)置想要設(shè)置的屬性即可,出錯的可能性很小,絕大部分場景用戶完全只需要更改__init__方法,用戶無需感知__new__的相關(guān)邏輯。
理論上是可以通過多次調(diào)用__init__函數(shù)進行初始化的,但是任何實例都只可能被創(chuàng)建一次,因為每次調(diào)用__new__函數(shù)理論上都是創(chuàng)建一個新實例返回(特殊情況如單例模式則只返回首次創(chuàng)建的實例),而不會存在重新構(gòu)造已有實例的情況。
針對__init__可被多次調(diào)用的情況,mutable和immutable對象會有不同的行為,因為immutable對象(不可變對象)從語義上來說首次創(chuàng)建、初始化完成后就不可以修改了,所以后續(xù)再調(diào)用其__init__方法應該無任何效果才對,示例如下:
a = [1, 2, 3] print(id(a), a) # 對list實例重新初始化改變其取值為[4, 5] a.__init__([4, 5]) print(id(a), a) b = (1, 2, 3) print(id(b), b) # 對tuple實例嘗試重新初始化并無任何效果, # 符合對immutable類型的行為預期 b.__init__((4, 5)) print(id(b), b)
執(zhí)行結(jié)果如下:

定義、繼承immutable class,tuple的栗子:
class PositiveTuple(tuple):
def __init__(self, *args, **kwargs):
print('get in init one, self:', id(self), self)
# 直接通過索引賦值的方式會報:
# PositiveTuple' object does not support item assignment
# for i, x in enumerate(self):
# self[i] = abs(x)
# 只能嘗試對self整體賦值
self = tuple(abs(x) for x in self)
print('get in init two, self:', id(self), self)
t = PositiveTuple([-3, -2, 5])
print(id(t), t)
執(zhí)行結(jié)果:

可以看到雖然在__init__中重新對self進行了賦值,其實只是相當于新生成了一個tuple對象28859528,t指向的依然是最開始生成好的實例28847512。
如下為使用自定義__new__的方法:
class PositiveTuple(tuple):
def __new__(cls, *args, **kwargs):
self = super().__new__(cls, *args, **kwargs)
print('get in init one, self:', id(self), self)
# 直接通過索引賦值的方式會報: PositiveTuple' object does not support item assignment
# for i, x in enumerate(self):
# self[i] = abs(x)
# 只能嘗試對self整體賦值
self = tuple(abs(x) for x in self)
print('get in init two, self:', id(self), self)
return self
t = PositiveTuple([-3, -2, 5])
print(id(t), t)
執(zhí)行結(jié)果如下:

可以看到一開始調(diào)用super.__new__時其實已經(jīng)創(chuàng)建了一個實例27667864,而后通過新生成一個全部轉(zhuǎn)化為正數(shù)的tuple 27679880賦值后返回,最終返回的實例t也就最終需要的全正數(shù)tuple。
(3)通過__new__方法實現(xiàn)單實例:
class Singleton(object):
def __new__(cls):
if not hasattr(cls, 'instance'):
cls.instance = super(Singleton, cls).__new__(cls)
# 每次生成的都是同一個實例
return cls.instance
s1 = Singleton()
s2 = Singleton()
s1.attr1 = 'xiaoxu'
print(s1.attr1, s2.attr1)
print(s1 is s2) # 返回True表明是同一個實例
print(s1 == s2)
# xiaoxu xiaoxu
# True
# True
一般比如字典使用==比較是比較值相等,is是比較地址相等。而在Class對象比較中,使用==和is都是比較地址相等(可以通過自定義__eq__來實現(xiàn)想要的效果)。
通過上述的分析,在實際應用中,我們通常就可以同時使用__new__和__init__方法來創(chuàng)建和初始化一個對象。通過重寫這兩個方法,我們可以自定義對象的創(chuàng)建和初始化過程,從而實現(xiàn)更加靈活和強大的功能。
到此這篇關(guān)于python中__new__和__init__的實現(xiàn)的文章就介紹到這了,更多相關(guān)python __new__和__init__內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- python面向?qū)ο笾衉_new__和__init__區(qū)別
- python中__new__函數(shù)的具體使用
- Python中的?__init__、__new__?和?__call__示例詳解
- Python中的魔術(shù)方法__new__詳解
- Python中__new__()方法適應及注意事項詳解
- 詳解Python中__new__和__init__的區(qū)別與聯(lián)系
- python __init__與 __new__的區(qū)別
- 詳解Python中__new__方法的作用
- Python 中類的構(gòu)造方法 __New__的妙用
- Python 中 __new__的實現(xiàn)示例
相關(guān)文章
python通過nmap掃描在線設(shè)備并嘗試AAA登錄(實例代碼)
這篇文章主要介紹了python通過nmap掃描在線設(shè)備并嘗試AAA登錄,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-12-12
Python中處理表格數(shù)據(jù)的Tablib庫詳解
這篇文章主要介紹了Python中處理表格數(shù)據(jù)的Tablib庫詳解,Tablib 是一個 MIT 許可的格式不可知的表格數(shù)據(jù)集庫,用 Python 編寫,它允許您導入、導出和操作表格數(shù)據(jù)集,需要的朋友可以參考下2023-08-08
Python和JavaScript間代碼轉(zhuǎn)換的4個工具
JavaScript 已經(jīng)成為眾多其它編程語言爭相選擇的轉(zhuǎn)換目標(相關(guān)實例包括 TypeScript、Emscripten、Cor 以及 Cheerp)。而 Python 則擁有龐大的追隨者群體,另外現(xiàn)有的強大庫資源則使其成為面向 JavaScript 的理想待轉(zhuǎn)換或者說轉(zhuǎn)譯選項2016-02-02
python+selenium小米商城紅米K40手機自動搶購的示例代碼
這篇文章主要介紹了python+selenium小米商城紅米K40手機自動搶購的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-03-03
Appium+python+unittest搭建UI自動化框架的實現(xiàn)
本文主要介紹了Appium+python+unittest搭建UI自動化框架的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2024-03-03
pycharm出現(xiàn)了pytest模式下如何改回run模式
這篇文章主要介紹了pycharm出現(xiàn)了pytest模式下如何改回run模式問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01

