我可以看到在 Python 中拥有属性的两种非常相似的方式
(a) 属性(property)类别
class Location(object):
def __init__(self, longitude, latitude):
self.set_latitude(latitude)
self.set_longitude(longitude)
def set_latitude(self, latitude):
if not (-90 <= latitude <= 90):
raise ValueError('latitude was {}, but has to be in [-90, 90]'
.format(latitude))
self._latitude = latitude
def set_longitude(self, longitude):
if not (-180 <= longitude <= 180):
raise ValueError('longitude was {}, but has to be in [-180, 180]'
.format(longitude))
self._longitude = longitude
def get_longitude(self):
return self._latitude
def get_latitude(self):
return self._longitude
latitude = property(get_latitude, set_latitude)
longitude = property(get_longitude, set_longitude)
(b) 属性装饰器
class Location(object):
def __init__(self, longitude, latitude):
self.latitude = latitude
self.longitude = latitude
@property
def latitude(self):
"""I'm the 'x' property."""
return self._latitude
@property
def longitude(self):
"""I'm the 'x' property."""
return self._longitude
@latitude.setter
def latitude(self, latitude):
if not (-90 <= latitude <= 90):
raise ValueError('latitude was {}, but has to be in [-90, 90]'
.format(latitude))
self._latitude = latitude
@longitude.setter
def longitude(self, longitude):
if not (-180 <= longitude <= 180):
raise ValueError('longitude was {}, but has to be in [-180, 180]'
.format(longitude))
self._longitude = longitude
题
这两段代码是否相同(例如字节码明智)?他们表现出相同的行为吗?
是否有任何官方指南使用哪种“风格”?
一个比另一个有什么真正的优势吗?
我试过的
py_compile + uncompyle6
我已经编译了两个:
>>> import py_compile
>>> py_compile.compile('test.py')
然后用 uncompyle6 反编译两者.但这恰好返回了我开始的内容(格式略有不同)
导入 + 显示
我试过
import test # (a)
import test2 # (b)
dis.dis(test)
dis.dis(test2)
我对
test2
的输出感到非常困惑:Disassembly of Location:
Disassembly of __init__:
13 0 LOAD_FAST 2 (latitude)
2 LOAD_FAST 0 (self)
4 STORE_ATTR 0 (latitude)
14 6 LOAD_FAST 2 (latitude)
8 LOAD_FAST 0 (self)
10 STORE_ATTR 1 (longitude)
12 LOAD_CONST 0 (None)
14 RETURN_VALUE
而第一个更大:
Disassembly of Location:
Disassembly of __init__:
13 0 LOAD_FAST 0 (self)
2 LOAD_ATTR 0 (set_latitude)
4 LOAD_FAST 2 (latitude)
6 CALL_FUNCTION 1
8 POP_TOP
14 10 LOAD_FAST 0 (self)
12 LOAD_ATTR 1 (set_longitude)
14 LOAD_FAST 1 (longitude)
16 CALL_FUNCTION 1
18 POP_TOP
20 LOAD_CONST 0 (None)
22 RETURN_VALUE
Disassembly of set_latitude:
17 0 LOAD_CONST 3 (-90)
2 LOAD_FAST 1 (latitude)
4 DUP_TOP
6 ROT_THREE
8 COMPARE_OP 1 (<=)
10 JUMP_IF_FALSE_OR_POP 18
12 LOAD_CONST 1 (90)
14 COMPARE_OP 1 (<=)
16 JUMP_FORWARD 4 (to 22)
>> 18 ROT_TWO
20 POP_TOP
>> 22 POP_JUMP_IF_TRUE 38
18 24 LOAD_GLOBAL 0 (ValueError)
26 LOAD_CONST 2 ('latitude was {}, but has to be in [-90, 90]')
28 LOAD_ATTR 1 (format)
30 LOAD_FAST 1 (latitude)
32 CALL_FUNCTION 1
34 CALL_FUNCTION 1
36 RAISE_VARARGS 1
19 >> 38 LOAD_FAST 1 (latitude)
40 LOAD_FAST 0 (self)
42 STORE_ATTR 2 (latitude)
44 LOAD_CONST 0 (None)
46 RETURN_VALUE
Disassembly of set_longitude:
22 0 LOAD_CONST 3 (-180)
2 LOAD_FAST 1 (longitude)
4 DUP_TOP
6 ROT_THREE
8 COMPARE_OP 1 (<=)
10 JUMP_IF_FALSE_OR_POP 18
12 LOAD_CONST 1 (180)
14 COMPARE_OP 1 (<=)
16 JUMP_FORWARD 4 (to 22)
>> 18 ROT_TWO
20 POP_TOP
>> 22 POP_JUMP_IF_TRUE 38
23 24 LOAD_GLOBAL 0 (ValueError)
26 LOAD_CONST 2 ('longitude was {}, but has to be in [-180, 180]')
28 LOAD_ATTR 1 (format)
30 LOAD_FAST 1 (longitude)
32 CALL_FUNCTION 1
34 CALL_FUNCTION 1
36 RAISE_VARARGS 1
24 >> 38 LOAD_FAST 1 (longitude)
40 LOAD_FAST 0 (self)
42 STORE_ATTR 2 (longitude)
44 LOAD_CONST 0 (None)
46 RETURN_VALUE
这种差异从何而来?第一个示例的值范围检查在哪里?
最佳答案
你总是想使用装饰器。其他语法没有优点,只有缺点。
装饰器的要点
那是因为装饰器语法是专门为避免其他语法而发明的。您找到的有关 name = property(...)
的任何示例多样性通常出现在早于装饰器的代码中。
装饰器语法是语法糖;表格
@decorator
def functionname(...):
# ...
执行得很像
def functionname(...):
# ...
functionname = decorator(functionname)
没有
functionname
被分配两次(def functionname(...)
部分创建一个函数对象并正常分配给 functionname
,但使用装饰器创建函数对象并直接传递给装饰器对象)。Python 增加了这个特性是因为当你的函数体很长的时候,你很难看出这个函数已经被装饰器包裹了。您必须向下滚动函数定义才能看到它,当您想了解的有关函数的几乎所有其他信息都在顶部时,这并不是很有帮助;参数、名称、文档字符串就在那里。
来自原文 PEP 318 – Decorators for Functions and Methods规范:
The current method of applying a transformation to a function or method places the actual transformation after the function body. For large functions this separates a key component of the function's behavior from the definition of the rest of the function's external interface.
[...]
This becomes less readable with longer methods. It also seems less than pythonic to name the function three times for what is conceptually a single declaration.
在设计目标下:
The new syntax should
- [...]
- move from the end of the function, where it's currently hidden, to the front where it is more in your face
所以使用
@property
def latitude(self):
# ...
@latitude.setter
def latitude(self, latitude):
# ...
比
def get_latitude(self):
# ...
def set_latitude(self, latitude):
# ...
latitude = property(get_latitude, set_latitude)
没有命名空间污染
接下来,因为
@property
装饰器将您装饰的函数对象替换为装饰结果(一个 property
实例),您还可以避免命名空间污染。无 @property
和 @<name>.setter
和 @<name>.deleter
,你必须在你的类定义中添加 3 个额外的、单独的名称,然后没有人会使用:>>> [n for n in sorted(vars(Location)) if n[:2] != '__']
['get_latitude', 'get_longitude', 'latitude', 'longitude', 'set_latitude', 'set_longitude']
想象一个具有 5 个、10 个甚至更多属性定义的类。对项目和自动完成 IDE 不太熟悉的开发人员肯定会对
get_latitude
之间的区别感到困惑。 , latitude
和 set_latitude
,并且您最终会得到混合样式的代码,并且现在更难摆脱在类级别公开这些方法。当然,您可以使用
del get_latitude, set_latitude
紧跟在 latitude = property(...)
之后分配,但这是更多额外的代码来执行,没有真正的目的。令人困惑的方法名称
尽管您可以避免在访问器名称前加上
get_
前缀。和 set_
或以其他方式区分名称以创建 property()
来自他们的对象,这仍然是几乎所有不使用 @property
的代码的方式。装饰器语法最终命名访问器方法。这可能会导致回溯中的一些困惑;在访问器方法之一中引发的异常导致回溯
get_latitude
或 set_latitude
在名称中,而前一行使用了 object.latitude
. Python 属性新手可能并不总是清楚这两者是如何连接的,特别是如果他们错过了 latitude = property(...)
再往下线;看上面。访问访问器,如何继承
您可能会指出无论如何您可能需要访问这些功能;例如,当只覆盖子类中属性的 getter 或 setter 时,同时继承其他访问器。
但是
property
对象,当在类上访问时,已经通过 .fget
为您提供了对访问器的引用。 , .fset
和 .fdel
属性:>>> Location.latitude
<property object at 0x10d1c3d18>
>>> Location.latitude.fget
<function Location.get_latitude at 0x10d1c4488>
>>> Location.latitude.fset
<function Location.set_latitude at 0x10d195ea0>
和 您可以重复使用
@<name>.getter
/@<name>.setter
/@<name>.deleter
子类中的语法,而不必记住创建新的 property
目的!使用旧语法,尝试仅覆盖其中一个访问器是司空见惯的:
class SpecialLocation(Location):
def set_latitude(self, latitude):
# ...
然后想知道为什么它不会被继承的
property
捡起来目的。使用装饰器语法,您将使用:
class SpecialLocation(Location):
@Location.latitude.setter
def latitude(self, latitude):
# ...
和
SpecialLocation
然后给子类一个新的 property()
具有从 Location
继承的 getter 的实例,并使用新的二传手。TLDR
使用装饰器语法。
关于python - 与属性类相比,使用属性装饰器是否有优势?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52899509/