geekdoc-python-zh/docs/askpython/python-property-decorator.md

312 lines
7.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 如何使用 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)
* * *