312 lines
7.1 KiB
Markdown
312 lines
7.1 KiB
Markdown
# 如何使用 Python 属性装饰器?
|
||
|
||
> 原文:<https://www.askpython.com/python/built-in-methods/python-property-decorator>
|
||
|
||
又见面了!在本文中,我们将看看 Python property decorator。
|
||
|
||
Python 有一个非常有用的特性,叫做 decorators,它只是函数包装器的语法糖。这里,我们将关注属性装饰器,这是一种特殊类型的装饰器。
|
||
|
||
这个主题可能会让你有点困惑,所以我们将使用说明性的例子一步一步地介绍它。我们开始吧!
|
||
|
||
* * *
|
||
|
||
## Python 属性装饰器基于什么?
|
||
|
||
属性装饰器基于内置的`property()`函数。这个函数返回一个特殊的`property`对象。
|
||
|
||
您可以在您的 Python 解释器中调用它并查看一下:
|
||
|
||
```py
|
||
>>> property()
|
||
<property object at 0x000002BBD7302BD8>
|
||
|
||
```
|
||
|
||
这个`property`对象有一些额外的方法,用于获取和设置对象的值。它也有删除它的方法。
|
||
|
||
方法列表如下:
|
||
|
||
* `property().getter`
|
||
* `property().setter`
|
||
* `property().deleter`
|
||
|
||
但这还不止于此!这些方法也可以用在其他对象上,它们本身也可以充当装饰者!
|
||
|
||
所以,举个例子,我们可以用`property().getter(obj)`,它会给我们另一个属性对象!
|
||
|
||
所以,需要注意的是属性装饰器将使用这个函数,它有一些特殊的方法来读写对象。但是这对我们有什么帮助呢?
|
||
|
||
现在让我们来看看。
|
||
|
||
* * *
|
||
|
||
## 使用 Python 属性装饰器
|
||
|
||
要使用属性装饰器,我们需要将它包装在任何函数/方法周围。
|
||
|
||
这里有一个简单的例子:
|
||
|
||
```py
|
||
@property
|
||
def fun(a, b):
|
||
return a + b
|
||
|
||
```
|
||
|
||
这与以下内容相同:
|
||
|
||
```py
|
||
def fun(a, b):
|
||
return a + b
|
||
|
||
fun = property(fun)
|
||
|
||
```
|
||
|
||
所以在这里,我们用`fun()`包装`property()`,这正是装饰者所做的!
|
||
|
||
现在让我们举一个简单的例子,在一个类方法上使用属性装饰器。
|
||
|
||
考虑下面的类,没有任何修饰的方法:
|
||
|
||
```py
|
||
class Student():
|
||
def __init__(self, name, id):
|
||
self.name = name
|
||
self.id = id
|
||
self.full_id = self.name + " - " + str(self.id)
|
||
|
||
def get_name(self):
|
||
return self.name
|
||
|
||
s = Student("Amit", 10)
|
||
print(s.name)
|
||
print(s.full_id)
|
||
|
||
# Change only the name
|
||
s.name = "Rahul"
|
||
print(s.name)
|
||
print(s.full_id)
|
||
|
||
```
|
||
|
||
**输出**
|
||
|
||
```py
|
||
Amit
|
||
Amit - 10
|
||
Rahul
|
||
Amit - 10
|
||
|
||
```
|
||
|
||
这里,正如你所看到的,当我们只改变对象的`name`属性时,它对`full_id`属性的引用仍然没有更新!
|
||
|
||
为了确保每当`name`或`id`更新时`full_id`属性也会更新,一个解决方案是将`full_id`变成一个方法。
|
||
|
||
```py
|
||
class Student():
|
||
def __init__(self, name, id):
|
||
self.name = name
|
||
self.id = id
|
||
|
||
def get_name(self):
|
||
return self.name
|
||
|
||
# Convert full_id into a method
|
||
def full_id(self):
|
||
return self.name + " - " + str(self.id)
|
||
|
||
s = Student("Amit", 10)
|
||
print(s.name)
|
||
# Method call
|
||
print(s.full_id())
|
||
|
||
s.name = "Rahul"
|
||
print(s.name)
|
||
# Method call
|
||
print(s.full_id())
|
||
|
||
```
|
||
|
||
**输出**
|
||
|
||
```py
|
||
Amit
|
||
Amit - 10
|
||
Rahul
|
||
Rahul - 10
|
||
|
||
```
|
||
|
||
这里,我们已经通过将`full_id`转换成方法`full_id()`解决了我们的问题。
|
||
|
||
然而,这并不是解决这个问题的最好方法,因为您可能需要将所有这样的属性转换成一个方法,并将这些属性转换成方法调用。这不方便!
|
||
|
||
为了减少我们的痛苦,我们可以使用`@property`装饰器来代替!
|
||
|
||
想法是把`full_id()`做成一个方法,但是用`@property`把它封装起来。这样,我们将能够更新`full_id`,而不必将其视为函数调用。
|
||
|
||
我们可以直接这样做:`s.full_id`。注意这里没有方法调用。这是因为属性装饰器。
|
||
|
||
让我们现在就来试试吧!
|
||
|
||
```py
|
||
class Student():
|
||
def __init__(self, name, id):
|
||
self.name = name
|
||
self.id = id
|
||
|
||
def get_name(self):
|
||
return self.name
|
||
|
||
@property
|
||
def full_id(self):
|
||
return self.name + " - " + str(self.id)
|
||
|
||
s = Student("Amit", 10)
|
||
print(s.name)
|
||
# No more method calls!
|
||
print(s.full_id)
|
||
|
||
s.name = "Rahul"
|
||
print(s.name)
|
||
# No more method calls!
|
||
print(s.full_id)
|
||
|
||
```
|
||
|
||
**输出**
|
||
|
||
```py
|
||
Amit
|
||
Amit - 10
|
||
Rahul
|
||
Rahul - 10
|
||
|
||
```
|
||
|
||
事实上,这种方法现在奏效了!现在,我们不需要使用括号调用`full_id`。
|
||
|
||
虽然它仍然是一个方法,但属性装饰器屏蔽了它,并将其视为[类](https://www.askpython.com/python/oops/python-classes-objects)的属性!现在名字没意义了吧!?
|
||
|
||
## 通过 setter 使用属性
|
||
|
||
在上面的例子中,这种方法是可行的,因为我们没有直接显式地修改`full_id`属性。默认情况下,使用`@property`会使该属性只读。
|
||
|
||
这意味着您不能显式更改该属性。
|
||
|
||
```py
|
||
s.full_id = "Kishore"
|
||
print(s.full_id)
|
||
|
||
```
|
||
|
||
**输出**
|
||
|
||
```py
|
||
---> 21 s.full_id = "Kishore"
|
||
22 print(s.full_id)
|
||
|
||
AttributeError: can't set attribute
|
||
|
||
```
|
||
|
||
显然,我们没有权限,因为该属性是只读的!
|
||
|
||
要让属性可写,还记得我们讲过的`property().setter`方法吗,也是 decorator?
|
||
|
||
原来我们可以使用`@full_id.setter`添加另一个`full_id`属性,使其可写。`@full_id.setter`将继承原来`full_id`的所有财产,所以我们可以直接添加!
|
||
|
||
但是,我们不能在 setter 属性中直接使用`full_id`属性。请注意,这将导致无限递归下降!
|
||
|
||
```py
|
||
class Student():
|
||
def __init__(self, name, id):
|
||
self.name = name
|
||
self.id = id
|
||
|
||
def get_name(self):
|
||
return self.name
|
||
|
||
@property
|
||
def full_id(self):
|
||
return self.name + " - " + str(self.id)
|
||
|
||
@full_id.setter
|
||
def full_id(self, value):
|
||
# Infinite recursion depth!
|
||
# Notice that you're calling the setter property of full_id() again and again
|
||
self.full_id = value
|
||
|
||
```
|
||
|
||
为了避免这种情况,我们将在我们的类中添加一个隐藏属性`_full_id`。我们将修改`@property`装饰器来返回这个属性。
|
||
|
||
更新后的代码将如下所示:
|
||
|
||
```py
|
||
class Student():
|
||
def __init__(self, name, id):
|
||
self.name = name
|
||
self.id = id
|
||
self._full_id = self.name + " - " + str(self.id)
|
||
|
||
def get_name(self):
|
||
return self.name
|
||
|
||
@property
|
||
def full_id(self):
|
||
return self._full_id
|
||
|
||
@full_id.setter
|
||
def full_id(self, value):
|
||
self._full_id = value
|
||
|
||
s = Student("Amit", 10)
|
||
print(s.name)
|
||
print(s.full_id)
|
||
|
||
s.name = "Rahul"
|
||
print(s.name)
|
||
print(s.full_id)
|
||
|
||
s.full_id = "Kishore - 20"
|
||
print(s.name)
|
||
print(s.id)
|
||
print(s.full_id)
|
||
|
||
```
|
||
|
||
**输出**
|
||
|
||
```py
|
||
Amit
|
||
Amit - 10
|
||
Rahul
|
||
Amit - 10
|
||
Rahul
|
||
10
|
||
Kishore - 20
|
||
|
||
```
|
||
|
||
我们已经成功地让`full_id`属性拥有了 getter 和 setter 属性!
|
||
|
||
* * *
|
||
|
||
## 结论
|
||
|
||
希望这能让您更好地理解属性装饰器,以及为什么您可能需要使用类属性,比如`_full_id`。
|
||
|
||
这些隐藏的类属性(像`_full_id`)让我们很容易使用外部的`full_id`属性!
|
||
|
||
这正是现代开源项目中大量使用属性的原因。
|
||
|
||
它们让最终用户非常容易,也让开发人员很容易将隐藏属性与非隐藏属性分离开来!
|
||
|
||
## 参考
|
||
|
||
* [StackOverflow 关于物业装修的问题](https://stackoverflow.com/questions/17330160/how-does-the-property-decorator-work-in-python)
|
||
|
||
* * * |