欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python中@property與@cached_property的實現

 更新時間:2025年08月29日 09:41:55   作者:青衫客36  
本文主要介紹了Python中@property與@cached_property的實現,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

在 Python 中,屬性不僅僅是簡單的數據字段,它們還可以是動態(tài)計算的值,并且可以封裝讀寫邏輯、懶加載、緩存控制,甚至權限檢查。本文將探討 Python 中與屬性控制相關的四個關鍵裝飾器:

  • @property
  • @x.setter
  • @x.deleter
  • @cached_property

一、@property—— 將方法變成屬性

基本用法

class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        return self._radius

    @property
    def area(self):
        return 3.1416 * self._radius ** 2

上述代碼中,radiusarea 都是方法,但通過 @property 裝飾后,可以像普通屬性一樣使用:

c = Circle(5)
print(c.radius)  # 5
print(c.area)    # 78.54

優(yōu)勢

方法調用形式屬性訪問形式好處
obj.get_value()obj.value語義清晰、接口簡潔

二、@x.setter和@x.deleter—— 為屬性添加寫入與刪除能力

?? 寫入控制:@x.setter

class Person:
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        if not value:
            raise ValueError("Name cannot be empty")
        self._name = value

@name.setter 允許我們對 name 屬性進行賦值時添加校驗邏輯:

p = Person("Alice")
p.name = "Bob"  # 正常
p.name = ""     # 拋出異常

?? 刪除控制:@x.deleter

class Secret:
    def __init__(self):
        self._token = "abc123"

    @property
    def token(self):
        return self._token

    @token.deleter
    def token(self):
        print("Token deleted")
        del self._token
s = Secret()
del s.token  # 調用自定義的刪除邏輯

三、@cached_property—— 懶加載 + 緩存

在處理高成本計算時,我們通常希望第一次調用時計算,之后讀取緩存結果。這正是 @cached_property 的應用場景。

示例:懶加載計算屬性

from functools import cached_property

class Expensive:
    @cached_property
    def result(self):
        print("Calculating...")
        return sum(i * i for i in range(10**6))

obj = Expensive()
print(obj.result)  # 第一次:執(zhí)行計算
print(obj.result)  # 第二次:返回緩存結果

四、cached_property的底層原理

源碼如下:

class cached_property:
    def __init__(self, func):
        self.func = func
        self.attrname = None
        self.__doc__ = func.__doc__
        self.lock = RLock()

    def __set_name__(self, owner, name):
        if self.attrname is None:
            self.attrname = name
        elif name != self.attrname:
            raise TypeError(
                "Cannot assign the same cached_property to two different names "
                f"({self.attrname!r} and {name!r})."
            )

    def __get__(self, instance, owner=None):
        if instance is None:
            return self
        if self.attrname is None:
            raise TypeError(
                "Cannot use cached_property instance without calling __set_name__ on it.")
        try:
            cache = instance.__dict__
        except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)
            msg = (
                f"No '__dict__' attribute on {type(instance).__name__!r} "
                f"instance to cache {self.attrname!r} property."
            )
            raise TypeError(msg) from None
        val = cache.get(self.attrname, _NOT_FOUND)
        if val is _NOT_FOUND:
            with self.lock:
                # check if another thread filled cache while we awaited lock
                val = cache.get(self.attrname, _NOT_FOUND)
                if val is _NOT_FOUND:
                    val = self.func(instance)
                    try:
                        cache[self.attrname] = val
                    except TypeError:
                        msg = (
                            f"The '__dict__' attribute on {type(instance).__name__!r} instance "
                            f"does not support item assignment for caching {self.attrname!r} property."
                        )
                        raise TypeError(msg) from None
        return val

cached_property實現了第一次調用時執(zhí)行函數,并將結果存入 __dict__,之后每次訪問直接從緩存中獲取。

然而,筆者在debug中發(fā)現self.attrname會變成函數名,這是為什么呢?

原來從 Python 3.6 開始,描述符類可以實現 __set_name__(self, owner, name) 方法,這個方法在類定義時被自動調用,用于告訴描述符它被賦值到哪個類屬性上。因為 cached_property 是一個描述符,并實現了 set_name 方法。

__set_name__(self, owner, name) 中的參數:

  • owner: 擁有這個屬性的類(類本身)
  • name: 被綁定到類上的屬性名(字符串)

當 @cached_property 被裝飾函數時(如 @cached_property def result(self): …),
Python 會在類創(chuàng)建期間(class 定義執(zhí)行時)自動調用這個 __set_name__,把方法名(如 “result”)作為 name 參數傳進來,從而完成 self.attrname = “result” 的綁定。

為什么這么設計?

這為 cached_property 提供了兩個關鍵能力:

  1. 動態(tài)識別屬性名,避免手動硬編碼;
  2. 支持多個 cached_property 實例在一個類中使用,每個都知道自己的名字。

能力 1:動態(tài)識別屬性名 ——避免手動寫死名字

當我們用 @cached_property 裝飾一個方法時,Python 自動把這個方法的名字(屬性名)告訴它,讓它知道“我是綁定在 xxx 上的”。

就像別人告訴你:“你現在的代號叫 result,記住它。”

這樣一來,cached_property 就知道以后把緩存結果存在 obj.__dict__["result"] 里,而不是自己去寫死 'result' 這個字符串。

如果沒有這個能力,我們需要手動這樣寫:

class cached_property:
    def __init__(self, name, func):
        self.func = func
        self.attrname = name  # 必須手動傳入
    ...

然后使用時也得這么寫:

class MyClass:
    result = cached_property("result", lambda self: 42)

不僅麻煩,而且容易出錯(比如名字不一致),所以 Python 自動通過 __set_name__() 把名字傳進去,非常方便且安全。

能力 2:支持多個cached_property實例在一個類中使用,每個都知道自己的名字

假設有以下一個類:

class User:
    @cached_property
    def profile(self): ...

    @cached_property
    def permissions(self): ...

這時 Python 會自動調用 __set_name__() 兩次

綁定 profile → attrname = "profile"
綁定 permissions → attrname = "permissions"

每個 cached_property 實例都知道它叫啥,它不會混淆。也就是說:

  • self.attrname = "profile" 存 profile 的結果在 __dict__["profile"]
  • self.attrname = "permissions" 存 permissions 的結果在 __dict__["permissions"]

如果沒有這個能力會怎么樣?

  • 我們就必須手動傳名字(見上面示例)
  • 容易寫錯,比如兩個地方都寫 cached_property("result", ...)
  • 系統(tǒng)就會誤把 profile 的緩存存在了 result

打個比方,cached_property 就像快遞員,快遞員一上崗(類定義時),老板(Python 解釋器)告訴他:“你負責投遞 result”,他以后就知道要把東西塞進instance.__dict__["result"],而不是瞎塞,每個快遞員有自己對應的“地址標簽”,不會互相搞錯。

能力舉例通俗比喻
動態(tài)識別屬性名自動綁定 方法Python 幫你貼了“標簽”
多實例支持類中定義多個 @cached_property每個快遞員有獨立投遞地址

?? 安全保護機制

在標準庫實現中,還有如下防御邏輯:

elif name != self.attrname:
	raise TypeError(
	     "Cannot assign the same cached_property to two different names "
	     f"({self.attrname!r} and {name!r})."
	 )

防止將同一個 cached_property 實例賦值到多個屬性名:

shared = cached_property(lambda self: 123)

class Foo:
    a = shared
    b = shared  # ? 拋出異常

執(zhí)行時會觸發(fā):

TypeError: Cannot assign the same cached_property to two different names ('a' and 'b')

為什么會報錯?因為 __set_name__() 是在類定義時自動調用的:

def __set_name__(self, owner, name):
    if self.attrname is None:
        self.attrname = name  # 第一次綁定成功
    elif name != self.attrname:
        raise TypeError(...)  # 第二次綁定,不一致就報錯

為什么不能這么做?因為cached_property 的核心機制是:

instance.__dict__[self.attrname] = self.func(instance)

如果 self.attrname 是模糊不定的、反復變,那它根本不知道應該往哪里緩存結果 —— 會導致緩存覆蓋、沖突、錯亂。

正確做法:為每個屬性寫一個新的實例

class Foo:
    @cached_property
    def a(self):
        return 1

    @cached_property
    def b(self):
        return 2

每次 @cached_property 都會創(chuàng)建一個新的實例,這就沒問題了。

總結

裝飾器是否只讀是否緩存是否可寫是否可刪除用途
@property????普通只讀屬性(動態(tài)計算)
@x.setter????提供 setter 邏輯
@x.deleter????提供屬性刪除邏輯
@cached_property???? (用 del)高成本、只讀緩存型屬性(如加載模型)

Python 的屬性系統(tǒng)遠比它表面上的 obj.x 更加強大。@property 提供了優(yōu)雅的接口封裝,@setter/@deleter 實現了對屬性的寫刪控制,而 @cached_property 則將惰性計算與緩存機制優(yōu)雅地融合到屬性系統(tǒng)之中。

到此這篇關于Python中@property與@cached_property的實現的文章就介紹到這了,更多相關Python @property @cached_property內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 在Python3 numpy中mean和average的區(qū)別詳解

    在Python3 numpy中mean和average的區(qū)別詳解

    今天小編就為大家分享一篇在Python3 numpy中mean和average的區(qū)別詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-08-08
  • Python 描述符(Descriptor)入門

    Python 描述符(Descriptor)入門

    本文給大家介紹的是Python中比較重要的一個知識點--描述符(Descriptor),描述符(descriptor)是Python語言核心中困擾我時間最長的一個特性,但是一旦你理解了之后,描述符的確還是有它的應用價值的。
    2016-11-11
  • Python中nonlocal的作用域靈活控制

    Python中nonlocal的作用域靈活控制

    nonlocal關鍵字為我們提供了一種在嵌套函數中訪問和修改外部非全局作用域變量的方式,本文主要介紹了Python中nonlocal的作用域靈活控制,具有一定的參考價值,感興趣的可以了解一下
    2025-04-04
  • python使用jieba實現中文分詞去停用詞方法示例

    python使用jieba實現中文分詞去停用詞方法示例

    jieba分詞,完全開源,有集成的python庫,簡單易用。下面這篇文章主要給大家介紹了關于python使用jieba實現中文分詞去停用詞的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面來一起看看吧。
    2018-03-03
  • Python collections中的雙向隊列deque簡單介紹詳解

    Python collections中的雙向隊列deque簡單介紹詳解

    這篇文章主要介紹了Python collections中的雙向隊列deque簡單介紹詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-11-11
  • PyQt5 在label顯示的圖片中繪制矩形的方法

    PyQt5 在label顯示的圖片中繪制矩形的方法

    今天小編就為大家分享一篇PyQt5 在label顯示的圖片中繪制矩形的方法,具有很好的參考價值。希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-06-06
  • python函數的5種參數詳解

    python函數的5種參數詳解

    昨天看《Python核心編程》的時候,剛好看到了函數部分,于是順勢將目前接觸到的集中參數類型都總結一下吧^^
    2017-02-02
  • python?繪制3D圖案例分享

    python?繪制3D圖案例分享

    這篇文章主要介紹了python?繪制3D圖案例分享,文章圍繞主題展開詳細的內容介紹,具有一定的參考價值,需要的小伙伴可以參考一下,希望對你的學習有所幫助
    2022-07-07
  • Python 無限級分類樹狀結構生成算法的實現

    Python 無限級分類樹狀結構生成算法的實現

    這篇文章主要介紹了Python 無限級分類樹狀結構生成算法的實現,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-01-01
  • python?sklearn與pandas實現缺失值數據預處理流程詳解

    python?sklearn與pandas實現缺失值數據預處理流程詳解

    對于缺失值的處理,主要配合使用sklearn.impute中的SimpleImputer類、pandas、numpy。其中由于pandas對于數據探索、分析和探查的支持較為良好,因此圍繞pandas的缺失值處理較為常用
    2022-09-09

最新評論