W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
你想將一個(gè)只讀屬性定義成一個(gè)property,并且只在訪問的時(shí)候才會計(jì)算結(jié)果。但是一旦被訪問后,你希望結(jié)果值被緩存起來,不用每次都去計(jì)算。
定義一個(gè)延遲屬性的一種高效方法是通過使用一個(gè)描述器類,如下所示:
class lazyproperty:
def __init__(self, func):
self.func = func
def __get__(self, instance, cls):
if instance is None:
return self
else:
value = self.func(instance)
setattr(instance, self.func.__name__, value)
return value
你需要像下面這樣在一個(gè)類中使用它:
import math
class Circle:
def __init__(self, radius):
self.radius = radius
@lazyproperty
def area(self):
print('Computing area')
return math.pi * self.radius ** 2
@lazyproperty
def perimeter(self):
print('Computing perimeter')
return 2 * math.pi * self.radius
下面在一個(gè)交互環(huán)境中演示它的使用:
>>> c = Circle(4.0)
>>> c.radius
4.0
>>> c.area
Computing area
50.26548245743669
>>> c.area
50.26548245743669
>>> c.perimeter
Computing perimeter
25.132741228718345
>>> c.perimeter
25.132741228718345
>>>
仔細(xì)觀察你會發(fā)現(xiàn)消息 Computing area
和 Computing perimeter
僅僅出現(xiàn)一次。
很多時(shí)候,構(gòu)造一個(gè)延遲計(jì)算屬性的主要目的是為了提升性能。例如,你可以避免計(jì)算這些屬性值,除非你真的需要它們。這里演示的方案就是用來實(shí)現(xiàn)這樣的效果的,只不過它是通過以非常高效的方式使用描述器的一個(gè)精妙特性來達(dá)到這種效果的。
正如在其他小節(jié)(如8.9小節(jié))所講的那樣,當(dāng)一個(gè)描述器被放入一個(gè)類的定義時(shí),每次訪問屬性時(shí)它的 __get__()
、__set__()
和 __delete__()
方法就會被觸發(fā)。不過,如果一個(gè)描述器僅僅只定義了一個(gè) __get__()
方法的話,它比通常的具有更弱的綁定。特別地,只有當(dāng)被訪問屬性不在實(shí)例底層的字典中時(shí) __get__()
方法才會被觸發(fā)。
lazyproperty
類利用這一點(diǎn),使用 __get__()
方法在實(shí)例中存儲計(jì)算出來的值,這個(gè)實(shí)例使用相同的名字作為它的property。這樣一來,結(jié)果值被存儲在實(shí)例字典中并且以后就不需要再去計(jì)算這個(gè)property了。你可以嘗試更深入的例子來觀察結(jié)果:
>>> c = Circle(4.0)
>>> # Get instance variables
>>> vars(c)
{'radius': 4.0}
>>> # Compute area and observe variables afterward
>>> c.area
Computing area
50.26548245743669
>>> vars(c)
{'area': 50.26548245743669, 'radius': 4.0}
>>> # Notice access doesn't invoke property anymore
>>> c.area
50.26548245743669
>>> # Delete the variable and see property trigger again
>>> del c.area
>>> vars(c)
{'radius': 4.0}
>>> c.area
Computing area
50.26548245743669
>>>
這種方案有一個(gè)小缺陷就是計(jì)算出的值被創(chuàng)建后是可以被修改的。例如:
>>> c.area
Computing area
50.26548245743669
>>> c.area = 25
>>> c.area
25
>>>
如果你擔(dān)心這個(gè)問題,那么可以使用一種稍微沒那么高效的實(shí)現(xiàn),就像下面這樣:
def lazyproperty(func):
name = '_lazy_' + func.__name__
@property
def lazy(self):
if hasattr(self, name):
return getattr(self, name)
else:
value = func(self)
setattr(self, name, value)
return value
return lazy
如果你使用這個(gè)版本,就會發(fā)現(xiàn)現(xiàn)在修改操作已經(jīng)不被允許了:
>>> c = Circle(4.0)
>>> c.area
Computing area
50.26548245743669
>>> c.area
50.26548245743669
>>> c.area = 25
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>>
然而,這種方案有一個(gè)缺點(diǎn)就是所有g(shù)et操作都必須被定向到屬性的 getter
函數(shù)上去。這個(gè)跟之前簡單的在實(shí)例字典中查找值的方案相比效率要低一點(diǎn)。如果想獲取更多關(guān)于property和可管理屬性的信息,可以參考8.6小節(jié)。而描述器的相關(guān)內(nèi)容可以在8.9小節(jié)找到。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: