[英]How best to implement a property where the setter can only be called once?
我試圖實現一個只能調用一次 setter 的類屬性,我想知道如何最好地實現這一點? 以及如何使它最“pythonic”?
我考慮過的選項:
property
。還有其他想法嗎?
和建議如何最好地實施?
如果您經常使用它以及其他property
功能,則子類化屬性是合適的。
由於屬性的工作方式,這有點棘手 - 當調用@prop.setter
,會創建該屬性的一個新實例。 下面的子類將起作用。
class FuseProperty(property):
def setter(self, func):
def fuse(instance, value):
name = f"_fuse_{self.fget.__name__}"
if not getattr(instance, name, False):
func(instance, value)
setattr(instance, name, True)
return super().setter(lambda instance, value: fuse(instance, value))
這是它在使用中。
In [24]: class A:
...: @FuseProperty
...: def a(self):
...: return self._a
...: @a.setter
...: def a(self, value):
...: self._a = value
...:
In [25]: a = A()
In [26]: a.a = 23
In [27]: a.a
Out[27]: 23
In [28]: a.a = 5
In [29]: a.a
Out[29]: 23
但是,如果這個“熔斷器”屬性就是你所需要的,並且沒有其他代碼被添加到 getter 和 setter 中,那么它可以簡單得多:你可以創建一個全新的“描述符”類,使用與property
相同的機制- 這可能會好得多,因為您的“保險絲”屬性可以在一行中構建,而無需 setter 和 getter 方法。
所需要的只是一個帶有__get__
和__set__
方法的類——我們可以添加__set_name__
以自動獲取新的屬性名稱(該property
本身沒有,所以我們從上面的fget
方法中獲取名稱)
class FuseAttribute:
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
if instance is None:
return self
return getattr(instance, f"_{self.name}")
def __set__(self, instance, value):
if not getattr(instance, f"_fuse_{self.name}", False):
setattr(instance, f"_{self.name}", value)
# add an else clause for optionally raising an error
setattr(instance, f"_fuse_{self.name}", True)
並使用它:
In [36]: class A:
...: a = FuseAttribute()
...:
In [37]: a = A()
In [38]: a.a = 23
In [39]: a.a
Out[39]: 23
In [40]: a.a = 5
In [41]: a.a
Out[41]: 23
我經常喜歡這種方式; “顯式優於隱式”:
class MyError(Exception):
...
NOT_SET = object()
class C:
def set_my_property(self, spam, eggs, cheese):
"""This sets the property.
If it's already set, you'll get an error. Donna do dat.
"""
if getattr(self, "_my_property", NOT_SET) is NOT_SET:
self._my_property = spam, eggs, cheese
return
raise MyError("I said, Donna do dat.")
@property
def my_property(self):
return self._my_property
測試:
c=C()
c.set_my_property("spam", "eggs", "cheese")
assert c.my_property == ("spam", "eggs", "cheese")
try:
c.set_my_property("bacon", "butter", "coffee")
except MyError:
pass
Python 中的屬性只是descriptors ,並且實現您自己的完全符合您的要求相對容易:
class SetOnceProperty:
def __init__(self, name):
self.storage_name = '_' + name
def __get__(self, obj, owner=None):
return getattr(obj, self.storage_name)
def __set__(self, obj, value):
if hasattr(obj, self.storage_name):
raise RuntimeError(f'{self.storage_name[1:]!r} property already set.')
setattr(obj, self.storage_name, value)
def __delete___(self, obj):
delattr(obj, self.storage_name)
class Test:
test_attr = SetOnceProperty('test_attr')
def __init__(self, value):
self.test_attr = value*2 # Sets property.
test = Test(21)
print(test.test_attr) # -> 42
test.test_attr = 13 # -> RuntimeError: 'test_attr' property already set.
這很簡單,也更通用。 只調用一次函數的裝飾器,忽略后續調用。
def onlyonce(func):
@functools.wraps(func)
def decorated(*args):
if not decorated.called:
decorated.called = True
return self.func(*args)
decorated.called = False
return decorated
像這樣使用
class A:
@property
def x(self):
...
@x.setter
@onlyonce
def x(self, val):
...
或者你可以定義一個描述符:
class Desc:
def __get__(self, inst, own):
return self._value
def __set__(self, inst, value):
if not hasattr(self, _value):
self._value = value
並像這樣使用:
class A:
x = Desc()
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.