在本教程中,您将通过简单易懂的示例了解 @property 装饰器,以及何时以及如何在 Python 中使用 @property 装饰器。
通过我们之前的教程,我们知道装饰器的主要目的是修改函数或类属性的行为,而类的使用者无需进行任何更改。我们在上一个教程中处理的装饰器是用户自定义的装饰器。为了使装饰更加简单,Python 提供了三个基本的内置装饰器,如 @classmethod、@staticmethod 和 @property 方法。在本教程中,我们将重点关注 Python 的 @property 装饰器。
@property 装饰器基本上是一个内置装饰器,其主要目的之一是将类方法用作属性,尽管它是一个带括号 () 的方法。.
让我们通过创建一个名为 License 的类来理解这一点。我们的 License 类包含 name、age 和 data 作为其属性。
class License:
def __init__(self,name,age):
self.name=name
self.age=age
self.data = '{} is {} years old'.format(self.name,self.age)
现在我们已经创建了类,下一步是为 License 类创建一个实例。这里 L 是具有两个值的实例,借助点运算符(.),我们可以访问类中属性的值,如下所示。
现在我们已经创建了类,下一步是为 License 类创建一个实例。这里 L 是具有两个值的实例,借助点运算符(.),我们可以访问类中属性的值,如下所示。
L = License('Tom',25)
print(L.name)
print(L.age)
print(L.data)
输出
Tom 25 Tom is 25 years old
当您观察代码片段时,您会发现 self.name 和 self.age 是主属性,而 self.data 是派生属性。
假设在某个阶段,您希望更改类中任何属性的值,并期望您所做的更改会反映在程序中使用该属性(已更改的属性)的任何地方。但实际上,这种情况不会发生。
在我们的例子中,假设您希望在实例化后更改 age,为此我们将 L.age 赋值为 15,如下所示。
L = License('Tom',25)
print(L.name)
L.age = 15 #assigned new age
print(L.age)
print(L.data)
实际上,预期的输出应该是这样的:L.age 为 15,L.data 为 Tom is 15 years old。但我们收到的输出是
Tom 15 Tom is 25 years old
在这里,您可以看到 age 属性更改为 15。但是 self.data 属性中的 age 保持不变,没有更新。
这个问题的原因是,在类中,如果您更改一个属性的值,从该类派生的其他属性不会自动更新。
因此,如果对类所做的更改不会在其派生类中自动更新,那么破坏代码的可能性很高。
那么我们如何解决这个问题呢?
解决此问题的一种方法是将类中的 self.data 属性转换为方法,即 getdata(),如下例所示。
class License:
def __init__(self,name,age):
self.name=name
self.age=age
def getdata(self):
return '{} is {} years old'.format(self.name,self.age )
现在,通过删除属性并添加新方法,该类已被修改。因此,实际上,我们的类现在没有名为 self.data 的属性,而是有一个方法 getdata()。
由于 getdata() 是一个方法,我们在调用此方法时必须小心。因为如果我们不带括号调用该方法,如下所示,我们将会遇到属性错误。
L = License('Tom',25) # Instance
print(L.name)
L.age = 15
print(L.age)
print(L.getdata) # missing parantheses,getdata is a method not an attribute
您将收到的输出将是
Tom 15 <bound method License.getdata of <__main__.License object at 0x038CB580>>
所以整个程序应该是这样的
class License:
def __init__(self,name,age):
self.name=name
self.age=age
def getdata(self):
return '{} is {} years old'.format(self.name,self.age )
L = License('Tom',25)
print(L.name)
L.age = 15
print(L.age)
print(L.getdata()) #calling method
输出
Tom 15 Tom is 15 years old
简而言之,所有实现上述类的程序都必须在遇到 self.data 的地方将其代码修改为 getdata()。如果客户端代码有成百上千行,那肯定会很麻烦。
因此,Python 提供的完美解决方案是将该方法转换为属性。这可以通过在方法定义之前添加一个 @property 装饰器来实现,它允许将方法用作属性。结果是,getdata() 方法可以作为属性访问,而不是带 () 的方法。以下示例说明了 @property 的使用。
class License:
def __init__(self,name,age):
self.name=name
self.age=age
@property
def getdata(self):
return '{} is {} years old'.format(self.name,self.age )
L = License('Tom',25)
print(L.name)
L.age = 15
print(L.age)
print(L.getdata) #calling method as attribute
这段代码片段会给你相同的输出,但在这里你可以看到我们在调用用 @property 装饰的方法时移除了括号 (),即 getdata()。因为通过使用 @property,getdata() 方法被作为属性访问,从而使我们的代码与客户端代码松散耦合。上述代码中的以下序列通常被称为 getter 方法,用于检索属性中的值。
@property
def getdata(self):
return '{} is {} years old'.format(self.name,self.age )
与 getter 方法一样,在 Python 中,我们有 setter 方法,用于为类中的属性设置值,从而确保数据封装的原则。
在定义 setter 方法时,您需要遵循一些规则,如下所列
@{方法名}.setter想象一下,我们的客户在他年满 18 岁时想要更改他的年龄,为了从客户端实现这一需求,我们需要在我们的类中引入 setter 方法属性,从而在客户不知情的情况下帮助他们。我们的程序也会相应地改变,如下所示
class License:
def __init__(self,name,age):
self.name=name
self.__age=age
@property
def getdata(self):
return self.name,self.__age
@getdata.setter
def getdata(self,age):
if age < 18:
print('Sorry {}, You are ineligible for License.'.format(self.name))
self.__age=age
else:
print('Congrats {},You are eligible for License'.format(self.name))
self.__age=age
L1 = License('Tom',25)
print('Before setting',L1.getdata)
L1.getdata=15 #age set to 15
print('After setting values changed to',L1.getdata)
print('-'*30)
L2 = License('Chris',17)
print('Before setting',L2.getdata)
L2.getdata=18 #age set to 18
print('After setting values changed to',L2.getdata)
输出
Before setting ('Tom', 25)
Sorry Tom, You are ineligible for License.
After setting values changed to ('Tom', 15)
------------------------------
Before setting ('Chris', 17)
Congrats Chris,You are eligible for License
After setting values changed to ('Chris', 18)
在这个程序中,我们添加了 setter 方法来验证年龄。通过检查 age 是否低于 18 岁。如果年龄低于 18 岁,类将打印消息说该人没有资格获得驾照;如果年龄超过 18 岁,则该人有资格获得驾照。
语句 self._age = age 将客户端提供的任何更新年龄设置到私有属性中,从而提供了数据隐藏功能。在我们的输出中,您可以看到 Tom 的年龄最初是 25 岁,后来被客户端设置为 15 岁。这也反映在我们的私有属性中。
所以现在,每当对基属性进行更改时,我们的类将自动更新派生属性,反之亦然。
与 @property 相关的另一个方法是 deleter 方法,它用于从内存中删除属性。
让我们在我们的 getdata 中实现 deleter 方法。与 setter 方法类似,实现 deleter 的特殊语法是 @{方法名}.setter,应在 deleter 方法之前提及。deleter 方法的名称应与应用 @decorator 的方法名称相同。
在这里,我们计划从内存中删除不需要的数据。在我们的例子中,我们正在删除不符合驾照资格的人员的数据。现在,在我们之前的示例中,我们添加了 deleter 方法,如下例所示。
class License:
def __init__(self,name,age):
self.name=name
self.__age=age
@property
def getdata(self):
return self.name,self.__age
@getdata.setter
def getdata(self,age):
if age < 18:
print('Sorry {}, You are ineligible for License.'.format(self.name))
self.__age=age
else:
print('Congrats {},You are eligible for License'.format(self.name))
self.__age=age
@getdata.deleter
def getdata(self):
del self.name
self.__age=None
L1 = License('Tom',25)
L2 = License('Chris',35)
print('Deleting....')
print(L1.getdata)
del L1.getdata
print(L2.name)
print(L1.age)
在这里,我们通过使用 del 关键字删除 name 属性来定义我们的 deleter 方法,另一种删除属性的方法是将属性 age 赋值为 None。
我们使用 del 关键字删除了我们的一个实例 L1,为了确认删除,我们尝试打印该实例中的 age。以下输出将清除所有疑问。
Deleting....
('Tom', 25)
Chris
Traceback (most recent call last):
File "C:\Users\TP-E540\Desktop\PY PRO\atpro_ex.py", line 188, in
print(L1.age)
AttributeError: 'License' object has no attribute 'age'
在 Python 中,我们有 property() 函数,可以用来代替 @property 装饰器。property() 函数是一个内置函数,它在 getter、setter 和 deleter 前面返回一个 property 属性。
语法
property() 的语法是
property(fget=None, fset=None, fdel=None, doc=None)
其中,
这四个函数参数是可选的。因此我们也可以简单地将 property() 创建为一个属性对象。
下面给出了一个简单的示例说明
class License:
def __init__(self, name,age):
self._name = name
self._age = age
def get_name(self):
print('Getting name and age')
return self._name ,self._age
def set_name(self, val):
print('Setting name to ', val)
self._name = val
def del_name(self):
print('Deleting name and age')
self._name = None
self._age = None
# Set property to use get_name, set_name
# and del_name methods
name = property(get_name, set_name, del_name, 'Data property')
L = License('Tom',25)
print(L.name)
L.name = 'Jerry'
del L.name
输出
Getting name and age
('Tom', 25)
Setting name to Jerry
Deleting name and age