溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Python調(diào)用機制是什么

發(fā)布時間:2021-11-20 17:02:33 來源:億速云 閱讀:137 作者:iii 欄目:編程語言

這篇文章主要介紹“Python調(diào)用機制是什么”,在日常操作中,相信很多人在Python調(diào)用機制是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Python調(diào)用機制是什么”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

下面的代碼會報錯,為什么?

class A(object):
 x = 1
 gen = (x for _ in xrange(10)) # gen=(x for _ in range(10))
if __name__ == "__main__":
 print(list(A.gen))

答案

這個問題是變量作用域問題,在 gen=(x for _ in xrange(10)) 中 gen 是一個 generator,在 generator 中變量有自己的一套作用域,與其余作用域空間相互隔離。因此,將會出現(xiàn)這樣的 NameError: name 'x' is not defined 的問題,那么解決方案是什么呢?答案是:用 lambda 。

class A(object):
 x = 1
 gen = (lambda x: (x for _ in xrange(10)))(x) # gen=(x for _ in range(10))
if __name__ == "__main__":
 print(list(A.gen))

裝飾器

描述

我想寫一個類裝飾器用來度量函數(shù)/方法運行時間

import time
class Timeit(object):
 def __init__(self, func):
 self._wrapped = func
 def __call__(self, *args, **kws):
 start_time = time.time()
 result = self._wrapped(*args, **kws)
 print("elapsed time is %s " % (time.time() - start_time))
 return result

這個裝飾器能夠運行在普通函數(shù)上:

@Timeit
def func():
 time.sleep(1)
 return "invoking function func"
if __name__ == '__main__':
 func() # output: elapsed time is 1.00044410133

但是運行在方法上會報錯,為什么?

class A(object):
 @Timeit
 def func(self):
 time.sleep(1)
 return 'invoking method func'
if __name__ == '__main__':
 a = A()
 a.func() # Boom!

如果我堅持使用類裝飾器,應(yīng)該如何修改?

答案

使用類裝飾器后,在調(diào)用 func 函數(shù)的過程中其對應(yīng)的 instance 并不會傳遞給 __call__ 方法,造成其 mehtod unbound ,那么解決方法是什么呢?描述符賽高

class Timeit(object):
 def __init__(self, func):
 self.func = func
 def __call__(self, *args, **kwargs):
 print('invoking Timer')
 def __get__(self, instance, owner):
 return lambda *args, **kwargs: self.func(instance, *args, **kwargs)

Python 調(diào)用機制

描述

我們知道 __call__ 方法可以用來重載圓括號調(diào)用,好的,以為問題就這么簡單?Naive!

class A(object):
 def __call__(self):
 print("invoking __call__ from A!")
if __name__ == "__main__":
 a = A()
 a() # output: invoking __call__ from A

現(xiàn)在我們可以看到 a() 似乎等價于 a.__call__() ,看起來很 Easy 對吧,好的,我現(xiàn)在想作死,又寫出了如下的代碼,

a.__call__ = lambda: "invoking __call__ from lambda"
a.__call__()
# output:invoking __call__ from lambda
a()
# output:invoking __call__ from A!

請大佬們解釋下,為什么 a() 沒有調(diào)用出 a.__call__() (此題由 USTC 王子博前輩提出)

答案

原因在于,在 Python 中,新式類( new class )的內(nèi)建特殊方法,和實例的屬性字典是相互隔離的,具體可以看看 Python 官方文檔對于這一情況的說明

For new-style classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary. That behaviour is the reason why the following code raises an exception (unlike the equivalent example with old-style classes):

同時官方也給出了一個例子:

class C(object):
 pass
c = C()
c.__len__ = lambda: 5
len(c)
# Traceback (most recent call last):
# File "", line 1, in 
# TypeError: object of type 'C' has no len()

回到我們的例子上來,當(dāng)我們在執(zhí)行 a.__call__=lambda:"invoking __call__ from lambda" 時,的確在我們在 a.__dict__ 中新增加了一個 key 為 __call__ 的 item,但是當(dāng)我們執(zhí)行 a() 時,因為涉及特殊方法的調(diào)用,因此我們的調(diào)用過程不會從 a.__dict__ 中尋找屬性,而是從 tyee(a).__dict__ 中尋找屬性。因此,就會出現(xiàn)如上所述的情況。

描述符

描述

我想寫一個 Exam 類,其屬性 math 為 [0,100] 的整數(shù),若賦值時不在此范圍內(nèi)則拋出異常,我決定用描述符來實現(xiàn)這個需求。

class Grade(object):
 def __init__(self):
 self._score = 0
 def __get__(self, instance, owner):
 return self._score
 def __set__(self, instance, value):
 if 0 <= 0="" 75="" 90="" value="" <="100:" self._score="value" else:="" raise="" valueerror('grade="" must="" be="" between="" and="" 100')="" exam(object):="" math="Grade()" def="" __init__(self,="" math):="" self.math="math" if="" __name__="=" '__main__':="" niche="Exam(math=90)" print(niche.math)="" #="" output="" :="" snake="Exam(math=75)" print(snake.math)="" snake.math="120" output:="" valueerror:grade="" 100!<="" code="">

看起來一切正常。不過這里面有個巨大的問題,嘗試說明是什么問題

為了解決這個問題,我改寫了 Grade 描述符如下:

class Grad(object):
 def __init__(self):
 self._grade_pool = {}
 def __get__(self, instance, owner):
 return self._grade_pool.get(instance, None)
 def __set__(self, instance, value):
 if 0 <= value="" <="100:" _grade_pool="self.__dict__.setdefault('_grade_pool'," {})="" _grade_pool[instance]="value" else:="" raise="" valueerror("fuck")<="" code="">

不過這樣會導(dǎo)致更大的問題,請問該怎么解決這個問題?

答案

1.第一個問題的其實很簡單,如果你再運行一次 print(niche.math) 你就會發(fā)現(xiàn),輸出值是 120 ,那么這是為什么呢?這就要先從 Python 的調(diào)用機制說起了。我們?nèi)绻{(diào)用一個屬性,那么其順序是優(yōu)先從實例的 __dict__ 里查找,然后如果沒有查找到的話,那么一次查詢類字典,父類字典,直到徹底查不到為止。好的,現(xiàn)在回到我們的問題,我們發(fā)現(xiàn),在我們的類 Exam中,其 self.math 的調(diào)用過程是,首先在實例化后的實例的 __dict__ 中進行查找,沒有找到,接著往上一級,在我們的類 Exam 中進行查找,好的找到了,返回。那么這意味著,我們對于 self.math 的所有操作都是對于類變量 math 的操作。因此造成變量污染的問題。那么該則怎么解決呢?很多同志可能會說,恩,在 __set__ 函數(shù)中將值設(shè)置到具體的實例字典不就行了。

那么這樣可不可以呢?答案是,很明顯不得行啊,至于為什么,就涉及到我們 Python 描述符的機制了,描述符指的是實現(xiàn)了描述符協(xié)議的特殊的類,三個描述符協(xié)議指的是 __get__ , ‘set‘ , __delete__ 以及 Python 3.6 中新增的 __set_name__ 方法,其中實現(xiàn)了 __get__ 以及 __set__ / __delete__ / __set_name__ 的是 Data descriptors ,而只實現(xiàn)了 __get__的是 Non-Data descriptor 。那么有什么區(qū)別呢,前面說了, 我們?nèi)绻{(diào)用一個屬性,那么其順序是優(yōu)先從實例的 __dict__ 里查找,然后如果沒有查找到的話,那么一次查詢類字典,父類字典,直到徹底查不到為止。 但是,這里沒有考慮描述符的因素進去,如果將描述符因素考慮進去,那么正確的表述應(yīng)該是我們?nèi)绻{(diào)用一個屬性,那么其順序是優(yōu)先從實例的 __dict__ 里查找,然后如果沒有查找到的話,那么一次查詢類字典,父類字典,直到徹底查不到為止。其中如果在類實例字典中的該屬性是一個 Data descriptors ,那么無論實例字典中存在該屬性與否,無條件走描述符協(xié)議進行調(diào)用,在類實例字典中的該屬性是一個 Non-Data descriptors ,那么優(yōu)先調(diào)用實例字典中的屬性值而不觸發(fā)描述符協(xié)議,如果實例字典中不存在該屬性值,那么觸發(fā) Non-Data descriptor 的描述符協(xié)議?;氐街暗膯栴},我們即使在 __set__ 將具體的屬性寫入實例字典中,但是由于類字典中存在著 Data descriptors ,因此,我們在調(diào)用 math 屬性時,依舊會觸發(fā)描述符協(xié)議。

2.經(jīng)過改良的做法,利用 dict 的 key 唯一性,將具體的值與實例進行綁定,但是同時帶來了內(nèi)存泄露的問題。那么為什么會造成內(nèi)存泄露呢,首先復(fù)習(xí)下我們的 dict 的特性,dict 最重要的一個特性,就是凡可 hash 的對象皆可為 key ,dict 通過利用的 hash 值的唯一性(嚴(yán)格意義上來講并不是唯一,而是其 hash 值碰撞幾率極小,近似認(rèn)定其唯一)來保證 key 的不重復(fù)性,同時(敲黑板,重點來了),dict 中的 key 引用是強引用類型,會造成對應(yīng)對象的引用計數(shù)的增加,可能造成對象無法被 gc ,從而產(chǎn)生內(nèi)存泄露。那么這里該怎么解決呢?兩種方法

第一種:

class Grad(object):
 def __init__(self):
 import weakref
 self._grade_pool = weakref.WeakKeyDictionary()
 def __get__(self, instance, owner):
 return self._grade_pool.get(instance, None)
 def __set__(self, instance, value):
 if 0 <= value="" <="100:" _grade_pool="self.__dict__.setdefault('_grade_pool'," {})="" _grade_pool[instance]="value" else:="" raise="" valueerror("fuck")<="" code="">

weakref 庫中的 WeakKeyDictionary 所產(chǎn)生的字典的 key 對于對象的引用是弱引用類型,其不會造成內(nèi)存引用計數(shù)的增加,因此不會造成內(nèi)存泄露。同理,如果我們?yōu)榱吮苊?value 對于對象的強引用,我們可以使用 WeakValueDictionary 。

第二種:在 Python 3.6 中,實現(xiàn)的 PEP 487 提案,為描述符新增加了一個協(xié)議,我們可以用其來綁定對應(yīng)的對象:

class Grad(object):
 def __get__(self, instance, owner):
 return instance.__dict__[self.key]
 def __set__(self, instance, value):
 if 0 <= value="" <="100:" instance.__dict__[self.key]="value" else:="" raise="" valueerror("fuck")="" def="" __set_name__(self,="" owner,="" name):="" self.key="name

這道題涉及的東西比較多,這里給出一點參考鏈接,invoking-descriptors , Descriptor HowTo Guide , PEP 487 , what`s new in Python 3.6 。

Python 繼承機制

描述

試求出以下代碼的輸出結(jié)果。

class Init(object):
 def __init__(self, value):
 self.val = value
class Add2(Init):
 def __init__(self, val):
 super(Add2, self).__init__(val)
 self.val += 2
class Mul5(Init):
 def __init__(self, val):
 super(Mul5, self).__init__(val)
 self.val *= 5
class Pro(Mul5, Add2):
 pass
class Incr(Pro):
 csup = super(Pro)
 def __init__(self, val):
 self.csup.__init__(val)
 self.val += 1
p = Incr(5)
print(p.val)

答案

輸出是 36 ,具體可以參考 New-style Classes , multiple-inheritance

Python 特殊方法

描述

我寫了一個通過重載 new 方法來實現(xiàn)單例模式的類。

class Singleton(object):
 _instance = None
 def __new__(cls, *args, **kwargs):
 if cls._instance:
 return cls._instance
 cls._isntance = cv = object.__new__(cls, *args, **kwargs)
 return cv
sin1 = Singleton()
sin2 = Singleton()
print(sin1 is sin2)
# output: True

現(xiàn)在我有一堆類要實現(xiàn)為單例模式,所以我打算照葫蘆畫瓢寫一個元類,這樣可以讓代碼復(fù)用:

class SingleMeta(type):
 def __init__(cls, name, bases, dict):
 cls._instance = None
 __new__o = cls.__new__
 def __new__(cls, *args, **kwargs):
 if cls._instance:
 return cls._instance
 cls._instance = cv = __new__o(cls, *args, **kwargs)
 return cv
 cls.__new__ = __new__o
class A(object):
 __metaclass__ = SingleMeta
a1 = A() # what`s the fuck

之前用這種方法給 __getattribute__ 打補丁的,下面這段代碼能夠捕獲一切屬性調(diào)用并打印參數(shù)

class TraceAttribute(type):
 def __init__(cls, name, bases, dict):
 __getattribute__o = cls.__getattribute__
 def __getattribute__(self, *args, **kwargs):
 print('__getattribute__:', args, kwargs)
 return __getattribute__o(self, *args, **kwargs)
 cls.__getattribute__ = __getattribute__
class A(object): # Python 3 是 class A(object,metaclass=TraceAttribute):
 __metaclass__ = TraceAttribute
 a = 1
 b = 2
a = A()
a.a
# output: __getattribute__:('a',){}
a.b

試解釋為什么給 getattribute 打補丁成功,而 new 打補丁失敗。

如果我堅持使用元類給 new 打補丁來實現(xiàn)單例模式,應(yīng)該怎么修改?

答案

其實這是最氣人的一點,類里的 __new__ 是一個 staticmethod 因此替換的時候必須以 staticmethod 進行替換。答案如下:

class SingleMeta(type):
 def __init__(cls, name, bases, dict):
 cls._instance = None
 __new__o = cls.__new__
 @staticmethod
 def __new__(cls, *args, **kwargs):
 if cls._instance:
 return cls._instance
 cls._instance = cv = __new__o(cls, *args, **kwargs)
 return cv
 cls.__new__ = __new__o
class A(object):
 __metaclass__ = SingleMeta
print(A() is A()) # output: True

到此,關(guān)于“Python調(diào)用機制是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI