最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

在 Python 中,屬性不僅僅是簡單的數(shù)據(jù)字段,它們還可以是動態(tài)計算的值,并且可以封裝讀寫邏輯、懶加載、緩存控制,甚至權(quán)限檢查。本文將探討 Python 中與屬性控制相關(guān)的四個關(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)勢

方法調(diào)用形式屬性訪問形式好處
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 屬性進(jì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—— 懶加載 + 緩存

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

示例:懶加載計算屬性

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

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

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

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

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

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

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

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

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

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

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

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

這樣一來,cached_property 就知道以后把緩存結(jié)果存在 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__() 把名字傳進(jìn)去,非常方便且安全。

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

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

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

    @cached_property
    def permissions(self): ...

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

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

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

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

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

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

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

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

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

在標(biāo)準(zhǔn)庫實現(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})."
	 )

防止將同一個 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__() 是在類定義時自動調(diào)用的:

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

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

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

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

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

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

    @cached_property
    def b(self):
        return 2

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

總結(jié)

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

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

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

相關(guān)文章

  • pycharm?console?打印中文為亂碼問題及解決

    pycharm?console?打印中文為亂碼問題及解決

    這篇文章主要介紹了pycharm?console?打印中文為亂碼問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • pycharm永久激活超詳細(xì)教程

    pycharm永久激活超詳細(xì)教程

    這篇文章主要介紹了pycharm永久激活超詳細(xì)教程,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-10-10
  • Python使用python-docx-template的實用案例解析

    Python使用python-docx-template的實用案例解析

    Python-docx-template是一個功能強大的Word文檔自動化生成庫,本文將進(jìn)一步結(jié)合官方示例,提供多個實用場景的代碼解析與拓展,希望對大家有所幫助
    2026-01-01
  • 如何高效使用Python字典的方法詳解

    如何高效使用Python字典的方法詳解

    Dictionary 是 Python 的內(nèi)置數(shù)據(jù)類型之一,它定義了鍵和值之間一對一的關(guān)系。下面這篇文章主要給大家介紹了關(guān)于如何高效使用Python字典的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-08-08
  • Python入門教程(十五)Python的字典

    Python入門教程(十五)Python的字典

    這篇文章主要介紹了Python入門教程(十五)Python的字典,Python是一門非常強大好用的語言,也有著易上手的特性,本文為入門教程,需要的朋友可以參考下
    2023-04-04
  • Python中json.loads和json.dumps方法中英雙語詳解

    Python中json.loads和json.dumps方法中英雙語詳解

    在Python中json.loads和json.dumps是處理JSON數(shù)據(jù)的重要方法,json.loads用于將JSON字符串解析為Python對象,而json.dumps用于將Python對象序列化為JSON字符串,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-01-01
  • python 自動識別并連接串口的實現(xiàn)

    python 自動識別并連接串口的實現(xiàn)

    這篇文章主要介紹了python 自動識別并連接串口的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • Pyscript使用本地Pyodide配置步驟

    Pyscript使用本地Pyodide配置步驟

    PyScript是“一個用于在 HTML(如 PHP)中交錯 Python 的系統(tǒng),這篇文章主要介紹了Pyscript使用本地Pyodide配置方法,需要的朋友可以參考下
    2022-12-12
  • Python常見庫matplotlib學(xué)習(xí)筆記之多個子圖繪圖

    Python常見庫matplotlib學(xué)習(xí)筆記之多個子圖繪圖

    Matplotlib是Python提供的一個繪圖庫,通過該庫我們可以很容易的繪制出折線圖、直方圖、散點圖、餅圖等豐富的統(tǒng)計圖,下面這篇文章主要給大家介紹了關(guān)于Python常見庫matplotlib學(xué)習(xí)筆記之多個子圖繪圖的相關(guān)資料,需要的朋友可以參考下
    2023-05-05
  • 對Python中TKinter模塊中的Label組件實例詳解

    對Python中TKinter模塊中的Label組件實例詳解

    今天小編就為大家分享一篇對Python中TKinter模塊中的Label組件實例詳解,具有很好的價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-06-06

最新評論

平山县| 慈利县| 龙川县| 美姑县| 盘锦市| 兖州市| 三门峡市| 界首市| 专栏| 即墨市| 清远市| 思茅市| 二手房| 北川| 江永县| 甘谷县| 宜春市| 汕头市| 同仁县| 芜湖县| 阜城县| 宜昌市| 汉川市| 平度市| 永修县| 航空| 墨玉县| 镇宁| 且末县| 天祝| 溆浦县| 鹤岗市| 台中县| 昌图县| 昂仁县| 平凉市| 保靖县| 巨鹿县| 嘉峪关市| 新源县| 荥阳市|