python中的专用@property装饰器

Posted

技术标签:

【中文标题】python中的专用@property装饰器【英文标题】:Specialized @property decorators in python 【发布时间】:2017-01-25 03:26:37 【问题描述】:

我有几个类,每个类都有许多属性。所有属性的共同点是它们应该是数字属性。这似乎是使用 python 装饰器的理想场所,但我似乎无法理解正确的实现是什么。这是一个简单的例子:

class Junk(object):
    def __init__(self, var):
        self._var = var

    @property
    def var(self):
        """A numeric variable"""
        return self._var

    @var.setter
    def size(self, value):
        # need to make sure var is an integer
        if not isinstance(value, int):
            raise ValueError("var must be an integer, var = ".format(value))
        self._var = value

    @var.deleter
    def size(self):
        raise RuntimeError("You can't delete var")

在我看来应该可以编写一个装饰器来完成所有操作,以便将上述内容转换为:

def numeric_property(*args, **kwargs):
    ...

class Junk(object):
    def __init__(self, var):
        self._var = var

    @numeric_property
    def var(self):
        """A numeric variable"""
        return self._var

这样新的numeric_property 装饰器可以在许多类中使用。

【问题讨论】:

numeric_property 会做什么property 还没有做?如果返回的值不是数字,则引发异常?严格类型真的不是 Python 风格。 IMO 在 Python 中强制执行它会使您的代码变得不灵活,例如Java,但仍然和 Python 一样慢。 你当然可以这样做,阅读docs.python.org/3/howto/descriptor.html 你为什么想让numeric_property 成为一个装饰者呢?为什么不只是一个返回属性的函数,所以你只需在你的类定义中做var = numeric_property('_var')? (如果你愿意,你可以让它接受文档字符串作为另一个参数。) 【参考方案1】:

选项1:继承自property

property 是一个描述符。见Descriptor HowTo on python.org。

因此,可以从property 继承并覆盖相关方法。

例如,在 setter 上强制执行 int:

class numeric_property(property):
    def __set__(self, obj, value):
        assert isinstance(value, int), "numeric_property requires an int"
        super(numeric_property, self).__set__(obj, value)

class A(object):
    @numeric_property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

现在你已经强制执行整数了:

>>> a = A()
>>> a.x = 'aaa'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __set__
AssertionError: numeric_property requires an int

选项 2:创建更好的描述符

另一方面,实现一个不从属性继承的全新描述符可能会更好,这样您就可以一次性定义属性。

有这样的界面会更好:

class A(object):
    x = numeric_property('_x')

为此,您将实现一个带有属性名称的描述符:

class numeric_property(object):
    def __init__(self, private_attribute_name, default=0):
        self.private_attribute_name = private_attribute_name
        self.default = default

    def __get__(self, obj, typ):
        if not obj: return self
        return getattr(obj, self.private_attribute_name, self.default)

    def __set__(self, obj, value):
        assert isinstance(value, int), "numeric_property requires an int"
        setattr(obj, self.private_attribute_name, value)

免责声明 :)

我宁愿不在 Pyhon 中强制执行严格类型,因为没有它,Python 会更强大。

【讨论】:

@jonrsharpe 这就是你所说的 touché。【参考方案2】:

您可以只创建一个为您执行此操作的函数。尽可能简单,无需创建自定义描述符:

def numprop(name, privname):
    @property
    def _numprop(self):
        return getattr(self, privname)

    @_numprop.setter
    def _numprop(self, value):
        if not isinstance(value, int):
            raise ValueError("name must be an integer, name = ".format(value, name=name))
        setattr(self, privname, value)

    @_numprop.deleter
    def _numprop(self):
        raise RuntimeError("You can't delete var")
    return _numprop


class Junk(object):
    def __init__(self, var):
        self._var = var
    var = numprop("var", "_var")

【讨论】:

“无需创建描述符” - 您正在创建描述符,但是以一种间接且尴尬的方式 我相信制作一个小函数比定制描述符更容易、更高效、更清晰。无需开始深入研究描述符协议。很多人知道属性是什么,但很少有人知道描述符。 你有权发表你的意见,但我不同意它更容易或更清晰(效率不是意见问题)。你所说的“深入研究描述符协议”我会改写为“知道你在做什么”。更不用说描述符类是可重用的,而返回单个属性的函数根本就不能。 我的朋友,你可以重新发明***,并为几乎所有东西创建自定义描述符和元类。如果您知道自己在做什么,还可以重新创建 typeobject。但话又说回来,对你来说简单的事情对 OP 来说不一定简单,否则他不会问这个问题。关于效率,使用内置函数几乎总是比定制类更快。您还可以向 OP 教授 __slots__ 和弱引用。 我会让 Raymond Hettinger 回复:“学习描述符不仅可以访问更大的工具集,还可以更深入地了解 Python 的工作原理以及对其优雅设计的欣赏... 描述符简化了底层 C 代码,并为日常 Python 程序提供了一组灵活的新工具。” 但是,同样,您有权发表自己的意见。【参考方案3】:

@property 只是 Python 的 descriptor protocol 的一个特例,因此您当然可以构建自己的自定义版本。对于您的情况:

class NumericProperty:
    """A property that must be numeric.

    Args:
      attr (str): The name of the backing attribute.

    """

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

    def __get__(self, obj, type=None):
        return getattr(obj, self.attr)

    def __set__(self, obj, value):
        if not isinstance(value, int):
            raise ValueError(" must be an integer, var = !r".format(self.attr, value))
        setattr(obj, self.attr, value)

    def __delete__(self, obj):
        raise RuntimeError("You can't delete ".format(self.attr))

class Junk:

    var = NumericProperty('_var')

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

使用中:

>>> j = Junk('hi')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/jonrsharpe/test.py", line 29, in __init__
    self.var = var
  File "/Users/jonrsharpe/test.py", line 17, in __set__
    raise ValueError(" must be an integer, var = !r".format(self.attr, value))
ValueError: _var must be an integer, var = 'hi'
>>> j = Junk(1)
>>> del j.var
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/jonrsharpe/test.py", line 21, in __delete__
    raise RuntimeError("You can't delete ".format(self.attr))
RuntimeError: You can't delete _var
>>> j.var = 'hello'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/jonrsharpe/test.py", line 17, in __set__
    raise ValueError(" must be an integer, var = !r".format(self.attr, value))
ValueError: _var must be an integer, var = 'hello'
>>> j.var = 2
>>> j.var
2

【讨论】:

你应该检查numbers.Integral而不是int,所以你可以使用任何数字类型。 @Daniel 这一点取决于 OP,我只是在展示如何将它们的逻辑实现为描述符。 FWIW 我可能也会把它设为TypeError @Daniel:或者,根据需要,只需调用 operator.index 以转换为真正的 Python int(或可能在 Py2 上为 long),所有 numbers.Integral 都将支持,并且为您提供可预测的确切类型。

以上是关于python中的专用@property装饰器的主要内容,如果未能解决你的问题,请参考以下文章

python内置装饰器@property

Python中的@property装饰器

Python中的property类和@property装饰器

Python,如何添加另一个装饰器来过滤现有多装饰器的输出与python中的属性?

Python get 和 set 方法与 @property 装饰器

Python装饰器之 property()