Mar*_*oma 5 python properties python-decorators
我可以看到两种在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)
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
这两段代码是否相同(例如字节码)?他们表现出同样的行为吗?
是否有任何"风格"使用的官方指南?
一个是否有任何真正的优势?
我编译了两个:
>>> import py_compile
>>> py_compile.compile('test.py')
Run Code Online (Sandbox Code Playgroud)
然后用uncompyle6反编译.但那刚刚回归我的开始(格式有点不同)
我试过了
import test # (a)
import test2 # (b)
dis.dis(test)
dis.dis(test2)
Run Code Online (Sandbox Code Playgroud)
我对以下输出感到非常困惑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
Run Code Online (Sandbox Code Playgroud)
而第一个更大:
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
Run Code Online (Sandbox Code Playgroud)
这种差异来自哪里?第一个例子的值范围检查在哪里?
你总是想使用装饰器。其他语法没有任何优点,只有缺点。
\n\n这是因为装饰器语法是专门为了避免其他语法而发明的。您发现的任何此类示例name = property(...)通常都在装饰器出现之前的代码中。
装饰器语法是语法糖;表格
\n\n@decorator\ndef functionname(...):\n # ...\nRun Code Online (Sandbox Code Playgroud)\n\n执行起来很像
\n\ndef functionname(...):\n # ...\n\nfunctionname = decorator(functionname)\nRun Code Online (Sandbox Code Playgroud)\n\n无需functionname分配两次(该def functionname(...)部分创建一个函数对象并正常分配functionname,但使用装饰器时,会创建函数对象并将其直接传递给装饰器对象)。
Python 添加此功能是因为当您的函数体很长时,您无法轻易看到该函数已被装饰器包装。您必须向下滚动到函数定义才能看到这一点,当您想了解的有关函数的几乎所有其他内容都位于顶部时,这并不是很有帮助;参数、名称、文档字符串就在那里。
\n\n来自原始PEP 318 \xe2\x80\x93函数和方法装饰器规范:
\n\n\n\n\n当前对函数或方法应用转换的方法将实际转换放在函数体之后。对于大型函数,这将函数行为的关键组件与函数外部接口的其余部分的定义分开。
\n\n[...]
\n\n对于较长的方法,这会变得不太可读。对于概念上的单个声明来说,将函数命名三次似乎也不太符合 Python 风格。
\n
并在设计目标下:
\n\n\n\n\n新语法应该
\n\n\n
\n- [...]
\n- 从当前隐藏的函数末尾移动到更靠近您的前面
\n
所以使用
\n\n@property\ndef latitude(self):\n # ...\n\n@latitude.setter\ndef latitude(self, latitude):\n # ...\nRun Code Online (Sandbox Code Playgroud)\n\n比以下内容更具可读性和自记录性
\n\ndef get_latitude(self):\n # ...\n\ndef set_latitude(self, latitude):\n # ...\n\nlatitude = property(get_latitude, set_latitude)\nRun Code Online (Sandbox Code Playgroud)\n\n接下来,因为@property装饰器用装饰结果(实例property)替换了你装饰的函数对象,所以你也避免了命名空间污染。如果没有@propertyand@<name>.setter和@<name>.deleter,您必须在类定义中添加3 个额外的单独名称,这样就没有人会使用它们:
>>> [n for n in sorted(vars(Location)) if n[:2] != \'__\']\n[\'get_latitude\', \'get_longitude\', \'latitude\', \'longitude\', \'set_latitude\', \'set_longitude\']\nRun Code Online (Sandbox Code Playgroud)\n\n想象一个具有 5 个、10 个甚至更多属性定义的类。不太熟悉该项目和自动完成 IDE 的开发人员肯定会对get_latitude、latitude和之间的差异感到困惑set_latitude,并且最终会得到混合样式的代码,并且现在很难摆脱在类级别公开这些方法。
当然,您可以在分配del get_latitude, set_latitude后立即使用latitude = property(...),但这仍然是更多没有实际目的的额外代码。
尽管您可以避免在访问器名称前添加前缀get_和set_或以其他方式区分名称以从中创建property()对象,但这仍然是几乎所有不使用@property装饰器语法的代码最终命名访问器方法的方式。
这可能会导致回溯中出现一些混乱;在其中一个访问器方法中引发的异常会导致名称中包含get_latitude或 的回溯,而前一行使用了. Python 属性新手可能并不总是清楚两者是如何连接的,特别是如果他们错过了下面的线;往上看。set_latitudeobject.latitudelatitude = property(...)
您可能会指出,无论如何您可能需要访问这些功能;例如,当仅重写子类中属性的 getter 或 setter 时,同时继承其他访问器。
\n\n但是property,当在类上访问该对象时,已经.fget通过和属性为您提供了对访问器的引用.fset:.fdel
>>> Location.latitude\n<property object at 0x10d1c3d18>\n>>> Location.latitude.fget\n<function Location.get_latitude at 0x10d1c4488>\n>>> Location.latitude.fset\n<function Location.set_latitude at 0x10d195ea0>\nRun Code Online (Sandbox Code Playgroud)\n\n并且您可以在子类中重用@<name>.getter//语法,而不必记住创建新对象!@<name>.setter@<name>.deleterproperty
使用旧语法,尝试仅覆盖其中一个访问器是很常见的:
\n\nclass SpecialLocation(Location):\n def set_latitude(self, latitude):\n # ...\nRun Code Online (Sandbox Code Playgroud)\n\n然后想知道为什么它不会被继承的对象拾取property。
使用装饰器语法,您可以使用:
\n\nclass SpecialLocation(Location):\n @Location.latitude.setter\n def latitude(self, latitude):\n # ...\nRun Code Online (Sandbox Code Playgroud)\n\n然后子SpecialLocation类被赋予一个新property()实例,其 getter 继承自Location,并具有新的 setter。
使用装饰器语法。
\n\n| 归档时间: |
|
| 查看次数: |
274 次 |
| 最近记录: |