如何用Python的方式为一个类的不同属性使用相同的getter和setter属性和函数?

lstz6jyr  于 2022-12-15  发布在  Python
关注(0)|答案(2)|浏览(92)

我有一个类,我正在处理它,它存储了Employees的详细信息,我希望所有的属性都能被保护,并且可以用特定的逻辑来设置和获取,但不是所有的都是以唯一的方式,我希望同样的逻辑可以应用到我的_f_name_l_name属性上,我希望同样的逻辑可以应用到接受布尔值和其他一般情况的属性上。
我得到了第一个属性:

@property
def f_name(self):
    return self.f_name
@f_name.setter
def f_name(self, f_name):
    if f_name != str(f_name):
        raise TypeError("Name must be set to a string")
    else:
        self._f_name = self._clean_up_string(f_name)
        
@f_name.deleter
def available(self):
    raise AttributeError("Can't delete, you can only change this value.")

如何将相同的函数和属性应用于其他属性?
谢谢!

piwo6bdm

piwo6bdm1#

虽然定义property的子类看起来是可能的,但是有太多关于 * 特定 * 属性如何工作的细节留给getter和setter来定义,这意味着定义一个定制的类似属性的描述符会更直接。

class CleanableStringProperty:
    def __set_name__(self, owner, name):
        self._private_name = "_" + name
        self.name = name

    def __get__(self, obj, objtype=None):
        # Boilerplate to handle accessing the property
        # via a class, rather than an instance of the class.
        if obj is None:
            return self
        return getattr(obj, self._private_name)

    def __set__(self, obj, value):
        if not isinstance(value, str):
            raise TypeError(f'{self.name} value must be a str')
        setattr(obj, self._private_name, obj._clean_up_string(value))

    def __delete__(self, obj):
        raise AttributeError("Can't delete, you can only change this value.")

__set_name__构造getter和setter将使用的示例属性的名称。__get__充当getter,使用getattr从给定对象检索构造的属性名称。__set__在使用setattr设置构造的属性名称之前验证并修改该值。__del__仅引发属性错误,而与调用者试图从哪个对象移除属性无关。
下面是一个简单的演示,它使所有分配给描述符的值都变成标题大小写。

class Foo:
    f_name = CleanableStringProperty()
    l_name = CleanableStringProperty()

    def __init__(self, first, last):
        self.f_name = first
        self.l_name = last

    def _clean_up_string(self, v):
        return v.title()

f = Foo("john", "doe")
assert f.f_name == "John"
assert f.l_name == "Doe"
try:
    del f.f_name
except AttributeError:
    print("Prevented first name from being deleted")

也可以将cleaning函数作为参数传递给CleanableStringProperty本身,而不是obj预期提供的函数。__init____set__将修改为

def __init__(self, cleaner):
    self.cleaner = cleaner

def __set__(self, obj, value):
    if not isinstance(value, str):
        raise TypeError(f'{self.name} value must be a str')
    setattr(obj, self._private_name, self.cleaner(value))

并且描述符将被初始化为

class Foo:
    fname = CleanableStringProperty(str.title)

请注意,Foo不再负责提供清洁方法。

rm5edbpk

rm5edbpk2#

属性只是描述符的实现,因此要创建自定义属性,需要一个具有__get____set__和/或__delete__方法的对象。
在您的情况下,您可以这样做:

from typing import Any, Callable, Tuple

class ValidatedProperty:
    def __set_name__(self, obj, name):
        self.name = name
        self.storage = f"_{name}"
    
    def __init__(self, validation: Callable[[Any], Tuple[str, Any]]=None):
        """Initializes a ValidatedProperty object

        Args:
            validation (Callable[[Any], Tuple[str, Any]], optional): A Callable that takes the given value and returns an error string (empty string if no error) and the cleaned-up value. Defaults to None.
        """
        self.validation = validation

    def __get__(self, instance, owner):
        return getattr(instance, self.storage)

    def __set__(self, instance, value):
        if self.validation: 
            error, value = self.validation(value)
            if error:
                raise ValueError(f"Error setting property {self.name}: {error}")

        setattr(instance, self.storage, value)

    def __delete__(self, instance):
        raise AttributeError("Can't delete, you can only change this value.")

让我们定义一个示例类来使用它:

class User:
    def __name_validation(value):
        if not isinstance(value, str):
            return (f"Expected string value, received {type(value).__name__}", None)

        return ("", value.strip().title())

    f_name = ValidatedProperty(validation=__name_validation)
    l_name = ValidatedProperty(validation=__name_validation)
    
    def __init__(self, fname, lname):
        self.f_name = fname

        self.l_name = lname

并测试:

u = User("Test", "User")

print(repr(u.f_name)) # 'Test'

u.f_name = 123  # ValueError: Error setting property f_name: Expected string value, received int

u.f_name = "robinson " # Notice the trailing space
print(repr(u.f_name))  # 'Robinson'

u.l_name = "crusoe "
print(repr(u.l_name))  # 'Crusoe'

相关问题