何时以及如何在Python中使用内置函数property()

85 投票
8 回答
40221 浏览
提问于 2025-04-15 15:00

我觉得除了语法上稍微好看一点,property() 其实没什么好处。

当然,能写 a.b=2 而不是 a.setB(2) 确实不错,但把 a.b=2 这个赋值操作隐藏起来,看起来会引发麻烦。比如说,可能会出现一些意想不到的结果,比如 a.b=2 实际上让 a.b 变成了 1。或者可能会抛出异常,或者造成性能问题,甚至让人感到困惑。

你能给我一个具体的例子,说明它的好用之处吗?(用它来修补有问题的代码不算哦;-)

8 个回答

15

但是隐藏 a.b=2 这个事实并不是一个简单的赋值,看起来会引发麻烦。

其实你并没有隐藏这个事实;这个事实根本就不存在。一切都是在 Python 这个高级语言中进行的,而不是在汇编语言里。在 Python 中,很多看似“简单”的语句实际上并不能直接转化为单一的 CPU 指令。把赋值看得过于简单,其实是在想一些根本不存在的东西。

当你说 x.b = c 时,你只需要理解“无论发生了什么,x.b 现在应该是 c”就可以了。

36

这个想法是让你在真正需要的时候再去写获取值和设置值的方法,而不是一开始就写。

所以,首先你可以这样写:

class MyClass(object):
    def __init__(self):
        self.myval = 4

显然,现在你可以直接写 myobj.myval = 5 来给值赋值。

但是后来,你决定确实需要一个设置值的方法,因为你想在设置值的时候做一些聪明的事情。不过,你不想去改动所有使用这个类的代码——所以你可以把设置值的方法用 @property 装饰器包裹起来,这样一切就能正常工作了。

150

在一些编程语言中,比如Java,使用“获取器”和“设置器”是很常见的。这些方法的作用就是简单明了的:比如说,x.getB() 这个方法应该只返回属性 b 的当前值,而 x.setB(2) 这个方法应该只做一些小的内部工作,让 x.getB() 返回 2

不过,语言本身并没有强制规定这些方法必须这样工作,也就是说,编译器并不会限制以 getset 开头的方法的具体实现。这些期望的行为主要依靠常识、社会习惯、风格指南和测试来维持。

在一些支持属性的语言中(比如Python),直接访问 x.b 或进行赋值 x.b = 2 的行为,和Java中的获取器和设置器是完全一样的:期望的行为相同,语言也没有强制的保证。

使用属性的第一个好处是语法和可读性。比如说,写成:

x.setB(x.getB() + 1)

而不是明显的:

x.b += 1

这让人感觉像是在向神灵复仇。在支持属性的语言中,根本没有理由让类的使用者经历这么复杂的代码,这只会影响代码的可读性,毫无好处。

特别是在Python中,使用属性(或其他描述符)代替获取器和设置器还有一个很大的好处:如果你重新组织了类,使得底层的设置器和获取器不再需要,你可以简单地删除这些方法和依赖它们的属性,而不会破坏类的公开接口,这样 b 就可以成为 x 类的一个普通“存储”属性,而不是一个需要计算的“逻辑”属性。

在Python中,直接操作(如果可行)而不是通过方法来做事情是一个重要的优化,系统地使用属性可以让你在可行时进行这种优化(总是直接暴露“正常存储属性”,而只有那些在访问或设置时需要计算的属性才通过方法和属性来处理)。

所以,如果你使用获取器和设置器而不是属性,不仅会影响用户代码的可读性,还会无谓地浪费计算机的资源(以及在这些计算周期中消耗的能量;-),而且这完全没有必要。

你唯一反对属性的理由可能是“外部用户通常不会期望赋值会有副作用”;但你忽略了一个事实,那就是在像Java这样的语言中,使用获取器和设置器的用户也不会期望调用设置器会有(可观察的)“副作用”(对于获取器更是如此;-)。这些都是合理的期望,作为类的作者,你应该尽量满足这些期望——无论你的设置器和获取器是直接使用还是通过属性使用,这一点没有区别。如果你有一些重要的可观察副作用的方法,绝对不要把它们命名为 getThissetThat,也不要通过属性来使用它们。

关于属性“隐藏实现”的抱怨是完全不合理的:面向对象编程(OOP)几乎所有的内容都是关于实现信息隐藏的——让一个类负责向外界展示一个逻辑接口,并尽可能好地在内部实现它。获取器和设置器,和属性一样,都是实现这个目标的工具。属性只是做得更好(在支持它们的语言中;-)。

撰写回答