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

Python中@property與@cached_property的實(shí)現(xiàn)

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

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

  • @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)勢(shì)

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

二、@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 允許我們對(duì) name 屬性進(jìn)行賦值時(shí)添加校驗(yàn)邏輯:

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  # 調(diào)用自定義的刪除邏輯

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

在處理高成本計(jì)算時(shí),我們通常希望第一次調(diào)用時(shí)計(jì)算,之后讀取緩存結(jié)果。這正是 @cached_property 的應(yīng)用場(chǎng)景。

示例:懶加載計(jì)算屬性

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í)行計(jì)算
print(obj.result)  # 第二次:返回緩存結(jié)果

四、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實(shí)現(xiàn)了第一次調(diào)用時(shí)執(zhí)行函數(shù),并將結(jié)果存入 __dict__,之后每次訪問直接從緩存中獲取。

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

原來從 Python 3.6 開始,描述符類可以實(shí)現(xiàn) __set_name__(self, owner, name) 方法,這個(gè)方法在類定義時(shí)被自動(dòng)調(diào)用,用于告訴描述符它被賦值到哪個(gè)類屬性上。因?yàn)?cached_property 是一個(gè)描述符,并實(shí)現(xiàn)了 set_name 方法。

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

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

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

為什么這么設(shè)計(jì)?

這為 cached_property 提供了兩個(gè)關(guān)鍵能力:

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

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

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

就像別人告訴你:“你現(xiàn)在的代號(hào)叫 result,記住它。”

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

如果沒有這個(gè)能力,我們需要手動(dòng)這樣寫:

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

然后使用時(shí)也得這么寫:

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

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

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

假設(shè)有以下一個(gè)類:

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

    @cached_property
    def permissions(self): ...

這時(shí) Python 會(huì)自動(dòng)調(diào)用 __set_name__() 兩次

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

每個(gè) cached_property 實(shí)例都知道它叫啥,它不會(huì)混淆。也就是說:

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

如果沒有這個(gè)能力會(huì)怎么樣?

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

打個(gè)比方,cached_property 就像快遞員,快遞員一上崗(類定義時(shí)),老板(Python 解釋器)告訴他:“你負(fù)責(zé)投遞 result”,他以后就知道要把東西塞進(jìn)instance.__dict__["result"],而不是瞎塞,每個(gè)快遞員有自己對(duì)應(yīng)的“地址標(biāo)簽”,不會(huì)互相搞錯(cuò)。

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

?? 安全保護(hù)機(jī)制

在標(biāo)準(zhǔn)庫實(shí)現(xiàn)中,還有如下防御邏輯:

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

防止將同一個(gè) cached_property 實(shí)例賦值到多個(gè)屬性名:

shared = cached_property(lambda self: 123)

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

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

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

為什么會(huì)報(bào)錯(cuò)?因?yàn)?__set_name__() 是在類定義時(shí)自動(dòng)調(diào)用的:

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

為什么不能這么做?因?yàn)閏ached_property 的核心機(jī)制是:

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

如果 self.attrname 是模糊不定的、反復(fù)變,那它根本不知道應(yīng)該往哪里緩存結(jié)果 —— 會(huì)導(dǎo)致緩存覆蓋、沖突、錯(cuò)亂。

正確做法:為每個(gè)屬性寫一個(gè)新的實(shí)例

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

    @cached_property
    def b(self):
        return 2

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

總結(jié)

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

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

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

相關(guān)文章

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

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

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

    Python 描述符(Descriptor)入門

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

    Python中nonlocal的作用域靈活控制

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

    python使用jieba實(shí)現(xiàn)中文分詞去停用詞方法示例

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

    Python collections中的雙向隊(duì)列deque簡(jiǎn)單介紹詳解

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

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

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

    python函數(shù)的5種參數(shù)詳解

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

    python?繪制3D圖案例分享

    這篇文章主要介紹了python?繪制3D圖案例分享,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下,希望對(duì)你的學(xué)習(xí)有所幫助
    2022-07-07
  • Python 無限級(jí)分類樹狀結(jié)構(gòu)生成算法的實(shí)現(xiàn)

    Python 無限級(jí)分類樹狀結(jié)構(gòu)生成算法的實(shí)現(xiàn)

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

    python?sklearn與pandas實(shí)現(xiàn)缺失值數(shù)據(jù)預(yù)處理流程詳解

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

最新評(píng)論