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")
【讨论】:
“无需创建描述符” - 您正在创建描述符,但是以一种间接且尴尬的方式 我相信制作一个小函数比定制描述符更容易、更高效、更清晰。无需开始深入研究描述符协议。很多人知道属性是什么,但很少有人知道描述符。 你有权发表你的意见,但我不同意它更容易或更清晰(效率不是意见问题)。你所说的“深入研究描述符协议”我会改写为“知道你在做什么”。更不用说描述符类是可重用的,而返回单个属性的函数根本就不能。 我的朋友,你可以重新发明***,并为几乎所有东西创建自定义描述符和元类。如果您知道自己在做什么,还可以重新创建type
和 object
。但话又说回来,对你来说简单的事情对 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类和@property装饰器
Python,如何添加另一个装饰器来过滤现有多装饰器的输出与python中的属性?