2 回答

TA貢獻1798條經驗 獲得超7個贊
緩存是實例字典本身,并且需要屬性的名稱作為鍵。所選設計施加了限制,但(IMO)這是一個很好的折衷方案。lazyprop不是線程安全的,或者至少self.func在多線程環境中調用的次數可能超過絕對必要的次數。
首先,它被記錄__set_name__在只有在賦值發生在類型創建的上下文中時才會被調用。文檔中的注釋(根據您的示例改編)顯示了您需要做的事情:給__set_name__自己打電話。
class D:
def __init__(self, greeting='Hello'):
self.greeting = greeting
def greet(self):
return self.greeting + " world!"
D.greet = cached_property(greet)
D.greet.__set_name__(D, 'greet') # sets D.greet.attrname = "greet" for later use
assert D().greet == "hello world!"
為什么需要屬性名稱?cached_property.__set__未定義,因此給定d = D(),d.greet將首先查找名為greetin的實例屬性d.__dict__。如果沒有找到,D.greet.__get__(d, D)則將被調用。該函數基本上做了三件事:如果需要計算值,它會調用原始greet函數,然后將其保存到同名的新實例屬性中,然后返回計算值。
“等等”,你會問,“ ‘如果需要’是什么意思?你剛才不是說D.greet.__get__只有當實例屬性不存在時才被調用嗎? ”是的,但是在多線程環境中,你不知道另一個線程是否也可能同時執行D.greet.__get__。為了防止競爭條件,__get__請執行以下步驟(如果您愿意,可以按照代碼進行操作):
檢查具有相同名稱的實例屬性,以防它是在當前調用開始后在另一個線程中創建的。
如果沒有,請嘗試獲取鎖,以便我們可以自己創建實例屬性。
獲得鎖后,再次查找實例屬性,以防在我們等待鎖時有人創建了它。
如果實例屬性仍然不存在,我們可以安全地自己創建它。
最后,無論我們從實例屬性中拉取一個值還是我們自己調用原始
greet
函數,我們都可以返回該值。
考慮到所有這些,我會稱這是一個限制而不是錯誤,而是一個很容易解決的限制。此實現可能比嘗試不依賴屬性名稱本身來維護必要映射的實現更簡單。

TA貢獻1856條經驗 獲得超11個贊
這既不是錯誤也不是限制。Using__set_name__只是推斷屬性名稱的一種不同方式——與常規類語法一起使用時更可靠。
例如,__set_name__也適用于僅cached_property直接綁定到名稱的匿名函數:
from functools import cached_property
import random
class Bar:
foo = cached_property(lambda self: random.random())
bar = bar()
print(bar.foo) # 0.9901613829744336
print(bar.foo) # 0.9901613829744336
相反,當使用時cached_property.cached_property,值存儲不當——即bar.<lambda>——阻止它隱藏屬性。
>>> functools_bar.__dict__
{'foo': 0.9901613829744336}
>>> cached_property_bar.__dict__
{'<lambda>': 0.7003011051281254}
添加回答
舉報