簡體   English   中英

如何最好地實現只能調用一次 setter 的屬性?

[英]How best to implement a property where the setter can only be called once?

我試圖實現一個只能調用一次 setter 的類屬性,我想知道如何最好地實現這一點? 以及如何使它最“pythonic”?

我考慮過的選項:

  1. 子類化並擴展內置property
  2. 裝飾一個屬性的 setter。
  3. 添加一個屬性,該屬性保持每個 setter 的設置頻率。

還有其他想法嗎?

和建議如何最好地實施?

如果您經常使用它以及其他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()

聽起來attrs項目對您的用例很有幫助。 您可以通過以下方式實現“凍結”屬性

import attr


@attr.s
class Test:
    constant = attr.ib(on_setattr=attr.setters.frozen)


test = Test('foo')
test.constant = 'bar'  # raises `attr.exceptions.FrozenAttributeError`

請注意,它還通過@constant.validator支持驗證器(請參閱attr.ib文檔末尾的attr.ib )。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM