gpt4 book ai didi

python - 与属性类相比,使用属性装饰器是否有优势?

转载 作者:太空狗 更新时间:2023-10-30 01:11:11 25 4
gpt4 key购买 nike

我可以看到在 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 之间的区别感到困惑。 , latitudeset_latitude ,并且您最终会得到混合样式的代码,并且现在更难摆脱在类级别公开这些方法。

当然,您可以使用 del get_latitude, set_latitude紧跟在 latitude = property(...) 之后分配,但这是更多额外的代码来执行,没有真正的目的。

令人困惑的方法名称

尽管您可以避免在访问器名称前加上 get_ 前缀。和 set_或以其他方式区分名称以创建 property()来自他们的对象,这仍然是几乎所有不使用 @property 的代码的方式。装饰器语法最终命名访问器方法。

这可能会导致回溯中的一些困惑;在访问器方法之一中引发的异常导致回溯 get_latitudeset_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/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com