是否有等效的属性部分方法?

Is there an equivalent of partialmethod for properties?

我有一个 class,其中有许多参数存储在字典中,如下所示(实际上,它有点复杂,但这提供了一个好主意)。我能够 'autogenerate' 使用 partialmethod:

获取和设置方法
for a_name in ['x', 'y', 'z']:
  locals()["get_" + a_name] = partialmethod(_get_arg, 
                                            arg_name=a_name) 
  locals()["set_" + a_name] = partialmethod(_set_arg, 
                                            arg_name=a_name) 

理想情况下,我想使用 @property,但前提是我可以 'autogenerate' @property@setter@deleter。可能吗?第二个片段显示 'manually' 添加的属性;我正在研究使用 partialmethod 的等价物来避免重复和不可维护的代码。

class C:
    def __init__(self):
        self.kwargs = {'x' : 0, 'y' : 3, 'z' : True}

    def __get_arg(self, arg_name):
        assert arg_name in self.kwargs
        return self.kwargs[arg_name]

    def __set_arg(self, arg_name, value):
        assert arg_name in self.kwargs
        self.kwargs[arg_name] = value

    def __del_arg(self, arg_name):
        assert arg_name in self.kwargs
        del self.kwargs[arg_name]

    @property
    def x(self):
        return self.__get_arg('x')

    @x.setter
    def x(self, value):
        self.__set_arg('x', value)

    @x.deleter
    def x(self):
        self.__del_arg('x')

    @property
    def y(self):
        return self.__get_arg('y')

    @y.setter
    def y(self, value):
        self.__set_arg('y', value)

    @y.deleter
    def y(self):
        self.__del_arg('y')

    @property
    def z(self):
        return self.__get_arg('z')

    @x.setter
    def z(self, value):
        self.__set_arg('z', value)

    @x.deleter
    def z(self):
        self.__del_arg('z')


c = C()
c.x = 'foo'  # setter called
foo = c.x    # getter called
del c.x      # deleter called

您可以编写自己的描述符类型,并使用将在其上调用的 __set_name__ 方法(由 class 创建机制)找出它在class:

class MyProp:
    def __set_name__(self, owner, name):
        self.name = name

    def __get__(self, instance, owner=None):
        if instance is None:
             return self
        return instance._get_arg(self.name)

    def __set__(self, instance, value):
        instance._set_arg(self.name, value)

    def __delete__(self, instance):
        instance._del_arg(self.name)

你会这样使用它:

 class C:
     # define __init__, _get_arg, etc.

     x = MyProp()
     y = MyProp()
     z = MyProp()

请注意,因为它是来自另一个 class 调用 _X_arg 方法的代码,您可能不想进行名称修改,所以我将 __ 前缀更改为只有一个 _.

经过一段时间的努力,我想出了以下 partialproperty 描述符。

访问属性时调用__get__方法。在其中我称之为 setter() 方法,第一个参数为 obj 作为 self 参数 接下来是解压后的 self.argsself.kwargs.

class partialproperty:
    """Combine the functionality of property() and partialmethod()"""
    def __init__(self, getter, setter=None, deleter=None, *args, **kwargs):
        self.getter = getter
        self.setter = setter
        self.deleter = deleter
        self.args = args
        self.kwargs = kwargs

    def __set_name__(self, owner, name):
        self._name = name
        self._owner = owner

    def __get__(self, obj, objtype=None):
        return self.getter(obj, *self.args, **self.kwargs)

    def __set__(self, obj, value):
        if self.setter is None:
            raise AttributeError(f"{self._owner.__class__.__name__} object can't set attribute: {self._name}")

        self.setter(obj, *self.args, value, **self.kwargs)

    def __delete__(self, obj):
        if self.deleter is None:
            raise AttributeError(f"{self._owner.__class__.__name__} object can't delete attribute: {self._name}")

        self.deleter(obj, *self.args, **self.kwargs)

您可以在 class 中使用它来创建 属性 属性,就像您一样 会用 partialmethod().

如果您不需要 setter 和删除器,您需要使用关键字 args 的参数,或显式传递 None.

class RGBA:
    def __init__(self, rgba):
        self.rgba = rgba

    def _component_(self, idx):
        return self.rgba[idx]

    # using keyword args
    red = partialproperty(_component_, idx=0)
    green = partialproperty(_component_, idx=1)
    blue = partialproperty(_component_, idx=2)

    # this has the same effect but with positional args
    alpha = partialproperty(_component_, None, None, 3)

然后在您的实例中,各个属性的行为将类似于属性。

>>> color = RGBA([0, 75, 255, 1])
>>> color.red
0
>>> color.green
75
>>> color.blue
255
>>> color.alpha
1

对于您的示例,它看起来像:

class C:
    def __init__(self, **kwargs):
        self.kwargs = kwargs

    def __get_arg(self, arg_name):
        try:
            return self.kwargs[arg_name]
        except KeyError:
            raise AttributeError(arg_name)

    def __set_arg(self, arg_name, value):
        self.kwargs[arg_name] = value

    def __del_arg(self, arg_name):
        try:
            del self.kwargs[arg_name]
        except KeyError:
            raise AttributeError(arg_name)

    x = partialproperty(__get_arg, __set_arg, __del_arg, "x")
    y = partialproperty(__get_arg, __set_arg, __del_arg, "y")
    z = partialproperty(__get_arg, __set_arg, __del_arg, "z")
>>> obj = C(x=24)
>>> obj.x
24

>>> obj.y = 25
>>> obj.y
25

>>> del obj.y
>>> obj.y
AttributeError: y

>>> obj.kwargs["z"] = 26
>>> obj.z
26

对于您的具体示例,更简单的方法可能是 subclass dict 然后定义魔术方法 __getattr____setattr____delattr__ 当属性不存在时调用它们。然后你 可以调用 super() 来继承 dict class.

的行为
class AttrDict(dict):
    attrs = ("x", "y", "z")
    def __getattr__(self, name):
        if not name in self.attrs:
            raise AttributeError(name)

        try:
            return super().__getitem__(name)
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        if not name in self.attrs:
            raise AttributeError(name)

        try:
            return super().__setitem__(name, value)
        except KeyError:
            raise AttributeError(name)

    def __delattr__(self, name):
        if not name in self.attrs:
            raise AttributeError(name)

        try:
            return super().__delitem__(name)
        except KeyError:
            raise AttributeError(name)
>>> obj = AttrDict(x=24)
>>> obj.x
24

>>> obj.a = 1
AttributeError: a

>>> obj.y = 25
>>> obj.y
25

>>> del obj.y
>>> obj.y
AttributeError: y

>>> obj.kwargs["z"] = 26
>>> obj.z
26

如果您不关心对特定属性的限制,那就更容易了。 您可以简单地将每个 __*attr__ 方法分配给它的对应方法 dict.__*item__ 方法,这是为订阅操作调用的方法。 (即obj[key]

class AttrDict(dict):
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__
>>> obj = AttrDict({"a": 1})
>>> obj.a
1

>>> obj["b"] = 2
>>> obj.b
2

>>> del obj.b
>>> obj.b
KeyError