我可以看到在Python中具有属性的两种非常相似的方式
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)
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
这两段代码是否相同(例如明智的字节码)?它们表现出相同的行为吗?
有没有官方指南使用哪种“样式”?
一个人比另一个人有什么真正的优势吗?
我都编译了:
>>> 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
区别从何而来?第一个示例的值范围检查在哪里?
答案 0 :(得分:2)
两个版本的代码的结果几乎完全相同。两种情况下,您最后拥有的属性描述符在功能上都是相同的。描述符中的唯一区别在于,如果您真的尝试过(通过Location.longitude.fset.__name__
)可以访问的函数名称,并且如果出了问题,可能会在异常回溯中看到。
唯一的不同是完成之后,get_foo
和set_foo
方法的存在。使用@property
时,不会有那些使名称空间混乱的方法。如果您自己手动构建property
对象,它们将保留在类名称空间中,因此,如果您确实愿意,可以直接调用它们,而不是通过property
对象使用普通的属性访问。>
通常,@property
语法更好,因为它隐藏了通常不需要的方法。我能想到的,您可能想要公开它们的唯一原因是,如果您希望将方法作为回调传递给其他函数(例如some_function(*args, callback=foo.set_longitude)
)。虽然您可以只将lambda
用于回调(lambda x: setattr(foo, "longitude", x)
),所以我认为仅在这种极端情况下,不应该使用多余的getter和setter方法来污染一个不错的API。
答案 1 :(得分:2)
总是要使用装饰器。其他语法没有优势,只有劣势。
那是因为装饰器语法是专门为避免其他语法而发明的。您发现的name = property(...)
种类的任何示例通常都在装饰器之前的代码中。
装饰器语法为语法糖;表格
@decorator
def functionname(...):
# ...
执行起来很像
def functionname(...):
# ...
functionname = decorator(functionname)
没有将functionname
分配给两次(def functionname(...)
部分会创建一个函数对象并正常分配给functionname
,但是使用装饰器会创建该函数对象并将其直接传递给装饰器对象)。
Python之所以添加此功能,是因为当函数主体为 long 时,您不容易看到该函数已被装饰器包装。您必须向下滚动到函数定义才能看到它,而当您想了解某个函数的几乎所有其他内容恰好位于顶部时,这并不是很有用。参数,名称和文档字符串就在那里。
根据原始的PEP 318 – Decorators for Functions and Methods规范:
当前将变换应用于函数或方法的方法将实际变换置于函数主体之后。对于大型函数,这会将函数行为的关键组成部分与其余函数外部接口的定义分开。
[...]
使用较长的方法时,这变得不太可读。对于从概念上讲是一个声明的函数,将函数命名三遍似乎也比pythonic少。
和设计目标下:
新语法应该
- [...]
- 从当前隐藏的功能的末尾移到您的脸部最前面
因此使用
@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
和{{1 }}属性:
.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()
继承的getter和一个新的setter。
使用装饰器语法。