Python 中的 property 属性

Python中有个很赞的概念,叫做property,它使得面向对象的编程更加简单。在详细解释和深入了解Python中的property之前,让我们首先建立这样一个直觉:为什么我们需要用到property?

从一个实例开始

假设有天你决定创建一个类,用来存储摄氏温度。当然这个类也需要实现一个将摄氏温度转换为华氏温度的方法。一种实现的方式如下:

我们可以用这个类产生一个对象,然后按照我们期望的方式改变该对象的温度属性:

这里额外的小数部分是转换成华氏温度时由于浮点运算误差造成的(你可以在Python解释器中试试1.1 + 2.2)。每当我们赋值或获取任何对象的属性时,例如上面展示的温度,Python都会从对象的__dict__字典中搜索它。

因此,man.temperature在其内部就变成了man.__dict__['temperature']

现在,让我们进一步假设我们的类在客户中很受欢迎,他们开始在其程序中使用这个类。他们对该类生成的对象做了各种操作。有一天,一个受信任的客户来找我们,建议温度不能低于-273摄氏度(热力学的同学可能会提出异议,它实际上是-273.15),也被称为绝对零。客户进一步要求我们实现这个值约束。作为一个以争取客户满意度为己任的公司,我们很高兴地听从了建议,发布了1.01版本,升级了我们现有的类。

使用Getters和Setters

对于上边的约束,一个很容易想到的解决方案是隐藏其温度属性(使其私有化),并且定义新的用于操作温度属性的getter和setter接口。可以这么实现:

从上边可以看出,我们定义了两个新方法get_temperature()set_temperature(),此外属性temperature也被替换为了_temperature。最前边的下划线(_)用于指示Python中的私有变量。

这个更新成功地实现了新约束,我们不再允许设置温度低于-273度。

请注意,Python中实际上是没有私有变量的。有一些简单的被遵循的规范。Python本身不会应用任何限制。

但这样并不会让人很放心。上述更新的最大问题是,所有在他们的程序中使用了我们先前类的客户都必须更改他们的代码:obj.temperature改为obj.get_temperature(),所有的赋值语句也必须更改,比如obj.temperature = val改为obj.set_temperature(val)。这样的重构会给那些拥有成千上万行代码的客户带来很大的麻烦。

总而言之,我们的更新是不向后兼容地。这就是需要property闪亮登场的地方。

Property的作用

对于上边的问题,Python式的解决方式是使用property。这里是我们已经实现了的一个版本:

我们在get_temperature()set_temperature()的内部增加了一个print()函数,用来清楚地观察它们是否正在执行。代码的最后一行,创建了一个property对象temperature。简单地说,property将一些代码(get_temperatureset_temperature)附加到成员属性(temperature)的访问入口。任何获取temperature值的代码都会自动调用get_temperature(),而不是去字典表(__dict__)中进行查找。同样的,任何赋给temperature值的代码也会自动调用set_temperature()。这是Python中一个很酷的功能。我们实际演示一下。

从上边的代码中我们可以看到,即使当我们创建一个对象时,set_temperature()也会被调用。你能猜到为什么吗?原因是,当一个对象被创建时,__init__()方法被调用。该方法有一行代码self.temperature = temperature。这个任务会自动调用set_temperature()方法。

同样的,对于属性的任何访问,例如c.temperature,也会自动调用get_temperature()方法。这就是property所作的事情。这里有一些额外的实例。

我们可以看到,通过使用property,我们在不需要客户代码做任何修改的情况下,修改了我们的类,并实现了值约束。因此我们的实现是向后兼容的,这样的结果,大家都很高兴。

最后需要注意的是,实际温度值存储在私有变量_temperature中。属性temperature是一个property对象,是用来为这个私有变量提供接口的。

深入挖掘property

在Python中,property()是一个内置函数,用于创建和返回一个property对象。该函数的签名为:

这里,fget是一个获取属性值的函数,fset是一个设置属性值的函数,fdel是一个删除属性的函数,doc是一个字符串(类似于注释)。从函数实现上看,这些函数参数都是可选的。所以,可以按照如下的方式简单的创建一个property对象。

Property对象有三个方法,getter(), setter()和delete(),用来在对象创建后设置fget,fset和fdel。这就意味着,这行代码:temperature = property(get_temperature,set_temperature)可以被分解为:

它们之间是相互等价的。

熟悉Python中装饰器decorator的程序员能够认识到上述结构可以作为decorator实现。我们可以更进一步,不去定义名字get_temperature和set_temperature,因为他们不是必须的,并且污染类的命名空间。为此,我们在定义getter函数和setter函数时重用名字temperature。下边的代码展示如何实现它。

上边的两种生成property的实现方式,都很简单,推荐使用。在Python寻找property时,你很可能会遇到这种类似的代码结构。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Pythonproperty是一种特殊的属性,它允许我们将一个方法作为属性来访问。这个方法可以用来获取、设置或删除属性的值。如果你想要实现类似于Javagetter和setter的功能,那么property属性就是非常有用的。 property属性通常用于控制类成员的访问,例如限制访问权限、验证输入、计算衍生数据等。 在Python,我们可以通过@property装饰器来创建一个属性。这个装饰器将一个方法转变为一个只读属性。如果我们想要实现可读写的属性,我们可以使用@property.setter装饰器来定义一个setter方法。同时,我们也可以使用@property.deleter装饰器来定义一个可以删除属性的方法。 下面是一个使用@property属性的例子: ```python class Person: def __init__(self, name, age): self._name = name self._age = age @property def name(self): return self._name @name.setter def name(self, value): self._name = value @property def age(self): return self._age @age.setter def age(self, value): if value < 0: raise ValueError("Age cannot be negative") self._age = value @property def is_adult(self): return self._age >= 18 ``` 在这个例子,我们定义了一个Person类,它有一个只读属性name,一个可读写属性age,以及一个只读属性is_adult。当我们访问name属性时,会调用name方法来获取属性的值。当我们尝试设置name属性时,会调用name.setter方法来设置属性的值。当我们访问age属性时,会调用age方法来获取属性的值。当我们尝试设置age属性时,会调用age.setter方法来设置属性的值。在age.setter方法,我们还加入了一些验证逻辑,确保age属性的值不能为负数。最后,我们定义了一个is_adult属性,它的值是根据age属性计算出来的。 通过使用@property属性,我们可以更加灵活地控制类成员的访问,从而实现更加严格的数据验证和更加精确的数据计算。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值