geekdoc-python-zh/docs/realpython/python-getter-setter.md

655 lines
31 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.

# Getters 和 Setters:在 Python 中管理属性
> 原文:<https://realpython.com/python-getter-setter/>
如果你来自像 Java 或 T2c++这样的语言,那么你可能习惯于为类中的每个属性编写 T4 getter 和 setter 方法。这些方法允许你访问和改变私有属性,同时保持**封装**。在 Python 中,通常会将属性作为公共 API 的一部分公开,并在需要具有函数行为的属性时使用**属性**。
尽管属性是 Pythonic 式的方法,但它们也有一些实际的缺点。因此,您会发现有些情况下 getters 和 setters 比属性更好。
**在本教程中,您将:**
* 在你的类中编写 **getter****setter** 方法
* 用**属性**替换 getter 和 setter 方法
* 探索其他工具来取代 Python 中的 getter 和 setter 方法
* 决定什么时候 **setter****getter** 方法可以成为作业的**正确工具**
为了充分利用本教程,您应该熟悉 Python [面向对象](https://realpython.com/python3-object-oriented-programming/)编程。如果你有 Python [属性](https://realpython.com/python-property/)和[描述符](https://realpython.com/python-descriptors/)的基础知识,那将是一个加分项。
**源代码:** [点击这里获取免费的源代码](https://realpython.com/bonus/python-getter-setter-code/),它向您展示了如何以及何时使用 Python 中的 getters、setters 和 properties。
## 了解 Getter 和 Setter 方法
当你在面向对象编程(OOP)中定义一个类时,你可能会以一些实例和类[属性](https://realpython.com/python3-object-oriented-programming/#class-and-instance-attributes)结束。这些属性只是可以通过实例、类或两者来访问的[变量](https://realpython.com/python-variables/)。
属性保存对象的内部[状态](https://en.wikipedia.org/wiki/State_(computer_science))。在许多情况下,您需要访问和改变这个状态,这涉及到访问和改变属性。通常,至少有两种方法可以访问和改变属性。您可以:
1. 直接访问并变异属性
*** 使用**方法**来访问和改变属性*
*如果您向您的用户公开一个类的属性,那么这些属性会自动成为该类的公共 [API](https://en.wikipedia.org/wiki/API) 的一部分。它们将是**公共属性**,这意味着你的用户将直接访问和改变他们代码中的属性。
如果您需要更改属性本身的内部实现,拥有一个属于类 API 的属性将会成为一个问题。这个问题的一个明显的例子是当你想把一个**存储的**属性变成一个**计算的**属性。存储属性将通过检索和存储数据来立即响应访问和突变操作,而计算属性将在这些操作之前运行计算。
常规属性的问题是它们不能有内部实现*,因为它们只是变量。因此,更改属性的内部实现需要将属性转换成方法,这可能会破坏用户的代码。为什么?因为如果他们希望代码继续工作,就必须在整个代码库中将属性访问和变异操作更改为方法调用。
为了处理这种问题,一些编程语言,如 Java 和 C++,要求你提供操作类属性的方法。这些方法通常被称为 **getter****setter** 方法。您还可以找到被称为[访问器](https://en.wikipedia.org/wiki/Accessor_method)和[赋值器](https://en.wikipedia.org/wiki/Mutator_method)的方法。
[*Remove ads*](/account/join/)
### 什么是 Getter 和 Setter 方法?
Getter 和 setter 方法在许多面向对象编程语言中非常流行。所以,很可能你已经听说过他们了。作为一个粗略的定义,你可以说 getters 和 setters 是:
* **Getter:** 一个允许你*访问*一个给定类中的属性的方法
* **Setter:** 一个方法,允许你*设置*或者*改变*一个类中属性的值
在 OOP 中getter 和 setter 模式表明,只有当你确定没有人需要将行为附加到公共属性时,才应该使用公共属性。如果一个属性可能改变它的内部实现,那么你应该使用 getter 和 setter 方法。
实现 getter 和 setter 模式需要:
1. 使您的属性成为非公共的
2. 为每个属性编写 getter 和 setter 方法
例如,假设您需要编写一个具有文本和字体属性的`Label`类。如果您要使用 getter 和 setter 方法来管理这些属性,那么您应该编写如下代码所示的类:
```py
# label.py
class Label:
def __init__(self, text, font):
self._text = text
self._font = font
def get_text(self):
return self._text
def set_text(self, value):
self._text = value
def get_font(self):
return self._font
def set_font(self, value):
self._font = value
```
在这个例子中,`Label`的构造函数有两个参数,`text`和`font`。这些参数分别存储在`._text`和`._font`非公共实例属性中。
然后为这两个属性定义 getter 和 setter 方法。通常getter 方法返回目标属性的值,而 setter 方法获取一个新值并将其赋给底层属性。
**注意:** Python 没有[访问修饰符](https://en.wikipedia.org/wiki/Access_modifiers)的概念,比如`private`、`protected`和`public`,来限制对类中属性和方法的访问。在 Python 中,区别在于**公共**和**非公共**类成员。
如果你想表明一个给定的属性或方法是非公共的,那么你应该使用 Python [的约定](https://www.python.org/dev/peps/pep-0008/#method-names-and-instance-variables),在名字前加一个下划线(`_`)。
注意,这只是一个约定。它不会阻止你和其他程序员使用**点符号**访问属性,就像在`obj._attr`中一样。然而,违反这个惯例是不好的。
您可以像下面的例子一样使用您的`Label`类:
>>>
```py
>>> from label import Label
>>> label = Label("Fruits", "JetBrains Mono NL")
>>> label.get_text()
'Fruits'
>>> label.set_text("Vegetables")
>>> label.get_text()
'Vegetables'
>>> label.get_font()
'JetBrains Mono NL'
```
对公共访问隐藏它的属性,而公开 getter 和 setter 方法。您可以在需要访问或变更类的属性时使用这些方法,正如您已经知道的,这些属性是非公共的,因此不是类 API 的一部分。
### Getter 和 Setter 方法从何而来?
为了理解 getter 和 setter 方法的来源,回到`Label`的例子,假设您想自动以大写字母存储标签的文本。不幸的是,您不能简单地将这种行为添加到像`.text`这样的常规属性中。您只能通过方法添加行为,但是将公共属性转换成方法会在您的 API 中引入一个**突破性的变化**。
那么,你能做什么?嗯,在 Python 中,你最有可能使用一个[属性](https://realpython.com/python-property/),你很快就会知道。然而,像 [Java](https://realpython.com/oop-in-python-vs-java/) 和 [C++](https://en.wikipedia.org/wiki/C%2B%2B) 这样的编程语言不支持类似属性的构造,或者它们的属性不太像 Python 属性。
这就是为什么这些语言鼓励你*永远不要将你的属性作为你的公共 API*的一部分。相反,你必须*提供 getter 和 setter 方法*,这提供了一种快速的方法来改变你的属性的内部实现,而不改变你的公共 API。
[封装](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming))是另一个与 getter 和 setter 方法起源相关的基本话题。本质上,这一原则指的是将数据与操作该数据的方法捆绑在一起。这样,访问和变异操作将只通过方法来完成。
该原则还与限制对对象属性的直接访问有关,这将防止暴露实现细节或违反状态不变性。
为了给`Label`提供 Java 或 C++中新需要的功能,必须从一开始就使用 getter 和 setter 方法。如何应用 getter 和 setter 模式来解决 Python 中的问题?
考虑以下版本的`Label`:
```py
# label.py
class Label:
def __init__(self, text, font):
self.set_text(text) self.font = font
def get_text(self):
return self._text
def set_text(self, value):
self._text = value.upper() # Attached behavior
```
在这个更新版本的`Label`中,您为标签的文本提供了 getter 和 setter 方法。保存文本的属性是**非公共属性**,因为它的名字前面有一个下划线`._text`。setter 方法执行输入转换,将文本转换成大写字母。
现在,您可以像下面的代码片段一样使用您的`Label`类:
>>>
```py
>>> from label import Label
>>> label = Label("Fruits", "JetBrains Mono NL")
>>> label.get_text()
'FRUITS'
>>> label.set_text("Vegetables")
>>> label.get_text()
'VEGETABLES'
```
酷!您已成功将所需行为添加到标签的文本属性中。现在你的 setter 方法有了一个真正的目标,而不仅仅是给 target 属性赋一个新值。它的目标是向`._text`属性添加额外的行为。
尽管 getter 和 setter 模式在其他编程语言中很常见,但在 Python 中却不是这样。
向类中添加 getter 和 setter 方法可以显著增加代码行数。Getters 和 setters 也遵循一种重复而枯燥的模式,需要额外的时间来完成。这种模式容易出错,而且很乏味。您还会发现,从所有这些额外代码中获得的即时功能通常是零。
所有这些听起来像是 Python 开发人员不想在他们的代码中做的事情。在 Python 中,您可能会编写类似于以下代码片段的`Label`类:
>>>
```py
>>> class Label:
... def __init__(self, text, font):
... self.text = text
... self.font = font
...
```
这里,`.text,`和`.font`是公共属性,作为类的 API 的一部分公开。这意味着您的用户可以随时更改他们的价值:
>>>
```py
>>> label = Label("Fruits", "JetBrains Mono NL")
>>> label.text
'Fruits'
>>> # Later...
>>> label.text = "Vegetables"
>>> label.text
'Vegetables'
```
暴露像`.text`和`.font`这样的属性是 Python 中的常见做法。因此,您的用户将在他们的代码中直接访问和改变这种属性。
像上面的例子一样,公开属性是 Python 中的一种常见做法。在这些情况下,切换到 getters 和 setters 将会引入突破性的变化。那么如何处理需要在属性中添加行为的情况呢Pythonic 式的方法是用属性替换属性。
[*Remove ads*](/account/join/)
## 使用属性代替 Getters 和 setter:Python 方式
将行为附加到属性的 Pythonic 方式是将属性本身变成一个**属性**。属性将获取、设置、删除和记录基础数据的方法打包在一起。因此,属性是具有附加行为的特殊属性。
您可以像使用常规属性一样使用属性。当您访问属性时,会自动调用其附加的 getter 方法。同样,当您变更属性时,会调用它的 setter 方法。这种行为提供了将功能附加到属性的方法,而不会在代码的 API 中引入重大更改。
作为属性如何帮助您将行为附加到属性的示例,假设您需要一个`Employee`类作为员工管理系统的一部分。您从以下基本实现开始:
```py
# employee.py
class Employee:
def __init__(self, name, birth_date):
self.name = name
self.birth_date = birth_date
# Implementation...
```
这个类的[构造函数](https://realpython.com/python-class-constructor/)有两个参数,手边雇员的姓名和出生日期。这些属性直接存储在两个实例属性中,`.name`和`.birth_date`。
您可以立即开始使用该类:
>>>
```py
>>> from employee import Employee
>>> john = Employee("John", "2001-02-07")
>>> john.name
'John'
>>> john.birth_date
'2001-02-07'
>>> john.name = "John Doe"
>>> john.name
'John Doe'
```
`Employee`允许您创建实例,以便直接访问相关的姓名和出生日期。注意,您也可以通过使用直接赋值来改变属性。
随着项目的发展,您会有新的需求。您需要用大写字母存储雇员的姓名,并将出生日期转换成一个 [`date`](https://realpython.com/python-datetime/) 对象。为了满足这些需求而不破坏您的 API使用`.name`和`.birth_date`的 getter 和 setter 方法,您可以使用属性:
```py
# employee.py
from datetime import date
class Employee:
def __init__(self, name, birth_date):
self.name = name
self.birth_date = birth_date
@property
def name(self): return self._name
@name.setter
def name(self, value): self._name = value.upper()
@property
def birth_date(self): return self._birth_date
@birth_date.setter
def birth_date(self, value): self._birth_date = date.fromisoformat(value)
```
在这个增强版的`Employee`中,使用`@property`装饰器将`.name`和`.birth_date`变成属性。现在每个属性都有一个 getter 和一个 setter 方法,以属性本身命名。注意,`.name`的 setter 把输入的名字变成了大写字母。同样,`.birth_date`的 setter 自动为你将输入的日期转换成一个`date`对象。
如前所述,属性的一个简洁的特性是,您可以将它们用作常规属性:
>>>
```py
>>> from employee import Employee
>>> john = Employee("John", "2001-02-07")
>>> john.name
'JOHN'
>>> john.birth_date
datetime.date(2001, 2, 7)
>>> john.name = "John Doe"
>>> john.name
'JOHN DOE'
```
酷!您已经向`.name`和`.birth_date`属性添加了行为,而没有影响您的类的 API。有了属性您就能够像引用常规属性一样引用这些属性。在幕后Python 负责为您运行适当的方法。
您必须避免通过在 API 中引入更改来破坏用户的代码。Python 的`@property` decorator 是实现这一点的 python 方式。在 [PEP 8](https://www.python.org/dev/peps/pep-0008/) 中,属性被正式推荐为处理需要功能行为的属性的正确方法:
> 对于简单的公共数据属性,最好只公开属性名,不要使用复杂的访问器/赋值器方法。请记住如果您发现一个简单的数据属性需要增加功能行为Python 为未来的增强提供了一个简单的途径。在这种情况下,使用属性将功能实现隐藏在简单的数据属性访问语法后面。([来源](https://peps.python.org/pep-0008/#designing-for-inheritance))
Python 的属性有很多潜在的用例。例如,您可以使用属性以优雅和简单的方式创建[只读](https://realpython.com/python-property/#providing-read-only-attributes)、[读写](https://realpython.com/python-property/#creating-read-write-attributes)和[只写](https://realpython.com/python-property/#providing-write-only-attributes)属性。属性允许您删除和记录基础属性等等。更重要的是,属性允许您使常规属性的行为像带有附加行为的托管属性一样,而不改变您使用它们的方式。
由于属性的原因Python 开发人员倾向于使用一些准则来设计他们的类的 API:
* 在适当的时候使用**公共属性**,即使您预期该属性在将来需要功能行为。
* **避免**为你的属性定义 **setter****getter** 方法。如果需要,您可以随时将它们转换为属性。
* 当你需要**将行为**附加到属性上并在你的代码中将它们作为常规属性使用时,使用**属性**。
* 避免属性中的副作用,因为没有人会期望像赋值这样的操作会产生任何副作用。
Python 的属性很酷!正因为如此,人们倾向于过度使用它们。通常,只有在需要在特定属性之上添加额外处理时,才应该使用属性。把你所有的属性都变成属性会浪费你的时间。这也可能意味着性能和可维护性问题。
[*Remove ads*](/account/join/)
## 用更高级的工具替换 Getters 和 Setters】
到目前为止,您已经学习了如何创建基本的 getter 和 setter 方法来管理类的属性。您还了解了属性是解决向现有属性添加功能行为问题的 Pythonic 方法。
在接下来的几节中,您将了解到可以用来替换 Python 中 getter 和 setter 方法的其他工具和技术。
### Python 的描述符
[描述符](https://realpython.com/python-descriptors/)是 Python 的一个高级特性,允许你在类中创建带有附加行为的属性。要创建一个描述符,你需要使用**描述符协议**,尤其是`.__get__()`和`.__set__()`T6】的特殊方法。
描述符非常类似于属性。事实上,属性是一种特殊类型的描述符。然而,常规描述符比属性更强大,可以通过不同的类重用。
为了说明如何使用描述符创建具有功能行为的属性,假设您需要继续开发您的`Employee`类。这一次,您需要一个属性来存储雇员开始为公司工作的日期:
```py
# employee.py
from datetime import date
class Employee:
def __init__(self, name, birth_date, start_date): self.name = name
self.birth_date = birth_date
self.start_date = start_date
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value.upper()
@property
def birth_date(self):
return self._birth_date
@birth_date.setter
def birth_date(self, value):
self._birth_date = date.fromisoformat(value)
@property def start_date(self): return self._start_date
@start_date.setter def start_date(self, value): self._start_date = date.fromisoformat(value)
```
在本次更新中,您向`Employee`添加了另一个属性。这个新属性将允许您管理每个员工的开始日期。同样setter 方法将日期从字符串转换成一个`date`对象。
这个类按预期工作。然而,它开始看起来重复和无聊。所以,你决定[重构](https://realpython.com/python-refactoring/)这个类。您注意到您在两个与日期相关的属性中执行相同的操作,并且您想到使用一个描述符来打包重复的功能:
```py
# employee.py
from datetime import date
class Date:
def __set_name__(self, owner, name):
self._name = name
def __get__(self, instance, owner):
return instance.__dict__[self._name]
def __set__(self, instance, value):
instance.__dict__[self._name] = date.fromisoformat(value)
class Employee:
birth_date = Date() start_date = Date()
def __init__(self, name, birth_date, start_date):
self.name = name
self.birth_date = birth_date
self.start_date = start_date
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value.upper()
```
这段代码比之前的版本更简洁,重复性更少。在这次更新中,您将创建一个`Date`描述符来管理与日期相关的属性。描述符有一个自动存储属性名的`.__set_name__()`方法。它还有`.__get__()`和`.__set__()`方法,分别作为属性的 getter 和 setter。
本节中的`Employee`的两个实现工作方式类似。来吧,给他们一个尝试!
一般来说,如果您发现自己的类中有相似的属性定义,那么您应该考虑使用描述符。
### `.__setattr__()`和`.__getattr__()`方法
另一种替代 Python 中传统 getter 和 setter 方法的方法是使用 [`.__setattr__()`](https://docs.python.org/3/reference/datamodel.html#object.__setattr__) 和 [`.__getattr__()`](https://docs.python.org/3/reference/datamodel.html#object.__getattr__) 特殊方法来管理属性。考虑下面的例子,它定义了一个`Point`类。该类自动将输入坐标转换为浮点数:
```py
# point.py
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __getattr__(self, name: str):
return self.__dict__[f"_{name}"]
def __setattr__(self, name, value):
self.__dict__[f"_{name}"] = float(value)
```
`Point`的[初始化器](https://realpython.com/python-class-constructor/#object-initialization-with-__init__)取两个坐标,`x`和`y`。`.__getattr__()`方法返回由`name`表示的坐标。为此,该方法使用实例名称空间字典, [`.__dict__`](https://docs.python.org/3/library/stdtypes.html#object.__dict__) 。请注意,属性的最终名称将在您在`name`中传递的任何内容之前有一个下划线。每当您使用点符号访问`Point`的属性时Python 会自动调用`.__getattr__()`。
`.__setattr__()`方法添加或更新属性。在这个例子中,`.__setattr__()`对每个坐标进行操作,并使用内置的`float()`函数将其转换为浮点数。同样每当您对包含类的任何属性运行赋值操作时Python 都会调用`.__setattr__()`。
下面是这个类在实践中的工作方式:
>>>
```py
>>> from point import Point
>>> point = Point(21, 42)
>>> point.x
21.0
>>> point.y
42.0
>>> point.x = 84
>>> point.x
84.0
>>> dir(point)
['__class__', '__delattr__', ..., '_x', '_y']
```
您的`Point`类自动将坐标值转换成浮点数。您可以访问坐标、`x`和`y`,就像访问任何其他常规属性一样。然而,访问和变异操作分别通过`.__getattr__()`和`.__setattr__()`。
请注意,`Point`允许您将坐标作为公共属性来访问。但是,它将它们存储为非公共属性。您可以使用内置的`dir()`函数来确认这一行为。
本节中的例子有点奇特,您可能不会在代码中使用类似的东西。但是,您在示例中使用的工具允许您对属性访问和变异执行验证或转换,就像 getter 和 setter 方法一样。
在某种意义上,`.__getattr__()`和`.__setattr__()`是 getter 和 setter 模式的一种通用实现。在幕后,这些方法充当 getters 和 setters支持 Python 中的常规属性访问和变异。
[*Remove ads*](/account/join/)
## 决定是否在 Python 中使用 Getters 和 Setters 或 Properties
在现实世界的编码中,您会发现 getter 和 setter 方法优于属性的一些用例,尽管属性通常是 Pythonic 的方式。
例如getter 和 setter 方法可能更适合处理您需要:
* **在属性访问或变异上运行代价高昂的转换**
* 带**额外参数**和**标志**
* 使用[继承](https://realpython.com/inheritance-composition-python/)
* 引发与属性访问和变异相关的[异常](https://realpython.com/python-exceptions/)
* 促进**异构**开发**团队**的整合
在接下来的部分中,您将深入这些用例,以及为什么 getter 和 setter 方法比属性更适合处理这些用例。
### 避免属性背后的缓慢方法
您应该避免将缓慢的操作隐藏在 Python 属性之后。API 的用户希望属性访问和变异像常规变量访问和变异一样执行。换句话说,用户将期望这些操作在*瞬间*发生,并且没有*副作用*。
离这个期望太远会让你的 API 使用起来奇怪和不愉快,违反了[最小惊奇原则](https://en.wikipedia.org/wiki/Principle_of_least_astonishment)。
此外,如果你的用户在一个[循环](https://realpython.com/python-for-loop/)中反复访问和改变你的属性,那么他们的代码会涉及太多的开销,这可能会产生巨大的*意想不到的*性能问题。
相比之下,传统的 getter 和 setter 方法使得*显式地*通过方法调用来访问或改变给定的属性。事实上,您的用户会意识到调用一个方法可能需要时间,并且他们的代码的性能可能会因此而有很大的差异。
在 API 中明确这些事实有助于减少用户在代码中访问和改变属性时的惊讶。
简而言之,如果你打算使用一个属性来管理一个属性,那么就要确保属性背后的方法是快速的,并且不会产生副作用。相比之下,如果您处理的是慢速访问器和赋值器方法,那么与属性相比,您更喜欢传统的 getters 和 setters 方法。
### 接受额外的参数和标志
与 Python 属性不同,传统的 getter 和 setter 方法允许更灵活的属性访问和变异。例如,假设您有一个带有`.birth_date`属性的`Person`类。这个属性在人的一生中应该是不变的。因此,您决定该属性将是只读的。
然而,由于人为错误的存在,您将面临有人在输入给定人员的出生日期时出错的情况。您可以通过提供一个带`force`标志的 setter 方法来解决这个问题,如下例所示:
```py
# person.py
class Person:
def __init__(self, name, birth_date):
self.name = name
self._birth_date = birth_date
def get_birth_date(self):
return self._birth_date
def set_birth_date(self, value, force=False):
if force:
self._birth_date = value
else:
raise AttributeError("can't set birth_date")
```
在这个例子中,您为`.birth_date`属性提供了传统的 getter 和 setter 方法。setter 方法带有一个名为`force`的额外参数,它允许您强制修改一个人的出生日期。
**注意:**传统的 setter 方法通常不会接受一个以上的参数。对于一些开发人员来说,上面的例子可能看起来很奇怪,甚至不正确。然而,它的目的是展示一种在某些情况下有用的技术。
这个类是这样工作的:
>>>
```py
>>> from person import Person
>>> jane = Person("Jane Doe", "2000-11-29")
>>> jane.name
'Jane Doe'
>>> jane.get_birth_date()
'2000-11-29'
>>> jane.set_birth_date("2000-10-29") Traceback (most recent call last):
...
AttributeError: can't set birth_date
>>> jane.set_birth_date("2000-10-29", force=True) >>> jane.get_birth_date()
'2000-10-29'
```
当您试图使用`.set_birth_date()`修改 Jane 的出生日期,而没有将`force`设置为`True`时,您会得到一个`AttributeError`,表示该属性无法设置。相反,如果您将`force`设置为`True`,那么您将能够更新 Jane 的出生日期,以纠正输入日期时出现的任何错误。
需要注意的是Python 属性不接受 setter 方法中的额外参数。它们只是接受要设置或更新的值。
[*Remove ads*](/account/join/)
### 使用继承:Getter 和 setter vs . Properties
Python 属性的一个问题是它们在继承场景中表现不佳。例如,假设您需要扩展或修改子类中属性的 getter 方法。实际上,没有安全的方法可以做到这一点。你不能只覆盖 getter 方法,并期望属性的其余功能保持与父类中的相同。
出现此问题是因为 getter 和 setter 方法隐藏在属性内部。它们不是独立遗传的,而是作为一个整体。因此,当您重写从父类继承的属性的 getter 方法时,您重写了整个属性,包括它的 setter 方法和它的其余内部组件。
例如,考虑以下类层次结构:
```py
# person.py
class Person:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
class Employee(Person):
@property
def name(self):
return super().name.upper()
```
在这个例子中,您覆盖了`Employee`中`.name`属性的 getter 方法。这样,您就隐式地覆盖了整个`.name`属性,包括它的 setter 功能:
>>>
```py
>>> from person import Employee
>>> jane = Employee("Jane")
>>> jane.name
'JANE'
>>> jane.name = "Jane Doe"
Traceback (most recent call last):
...
AttributeError: can't set attribute 'name'
```
现在`.name`是一个只读属性,因为父类的 setter 方法没有被继承,而是被一个全新的属性覆盖。你不想那样,是吗?你如何解决这个继承问题?
如果您使用传统的 getter 和 setter 方法,那么这个问题就不会发生:
```py
# person.py
class Person:
def __init__(self, name):
self._name = name
def get_name(self):
return self._name
def set_name(self, value):
self._name = value
class Employee(Person):
def get_name(self):
return super().get_name().upper()
```
这个版本的`Person`提供了独立的 getter 和 setter 方法。`Employee`子类`Person`,覆盖 name 属性的 getter 方法。这个事实并不影响 setter 方法,该方法`Employee`成功地从其父类`Person`继承而来。
下面是这个新版本的`Employee`的工作原理:
>>>
```py
>>> from person import Employee
>>> jane = Employee("Jane")
>>> jane.get_name()
'JANE'
>>> jane.set_name("Jane Doe")
>>> jane.get_name()
'JANE DOE'
```
现在`Employee`已经完全可以使用了。被重写的 getter 方法按预期工作。setter 方法也可以工作,因为它是从`Person`成功继承的。
### 在属性访问或突变时引发异常
大多数时候,你不会想到像`obj.attribute = value`这样的赋值语句会引发异常。相比之下,您可以期望方法在响应错误时引发异常。在这方面,传统的 getter 和 setter 方法比属性更显式。
例如,`site.url = "123"`看起来不像是可以引发异常的东西。它看起来应该像一个常规的属性赋值。另一方面,`site.set_url("123")`看起来确实像是可以引发异常的东西,也许是一个`ValueError`,因为输入值不是一个网站的有效的 [URL](https://en.wikipedia.org/wiki/URL) 。在这个例子中setter 方法更加明确。它清楚地表达了代码可能的行为。
根据经验,除非使用属性来提供只读属性,否则应避免在 Python 属性中引发异常。如果您需要在属性访问或变异时引发异常,那么您应该考虑使用 getter 和 setter 方法,而不是属性。
在这些情况下,使用 getters 和 setters 将减少用户的惊讶,并使您的代码更符合常见的实践和期望。
### 促进团队整合和项目迁移
在许多成熟的编程语言中,提供 getter 和 setter 方法是常见的做法。如果你和一个来自其他语言背景的开发团队一起开发一个 Python 项目,那么很可能 getter 和 setter 模式对他们来说比 Python 属性更熟悉。
在这种类型的异构团队中,使用 getters 和 setters 可以促进新开发人员融入团队。
使用 getter 和 setter 模式也可以提高 API 的一致性。它允许您提供基于方法调用的 API而不是将方法调用与直接属性访问和变异相结合的 API。
通常,当 Python 项目增长时,您可能需要将项目从 Python 迁移到另一种语言。新语言可能没有属性,或者它们的行为可能不像 Python 属性那样。在这些情况下,从一开始就使用传统的 getters 和 setters 会使将来的迁移不那么痛苦。
在上述所有情况下,您应该考虑使用传统的 getter 和 setter 方法,而不是 Python 中的属性。
[*Remove ads*](/account/join/)
## 结论
现在你知道什么是 **getter****setter** 方法,以及它们来自哪里。这些方法允许访问和改变属性,同时避免 API 的改变。然而,由于属性的存在,它们在 Python 中并不流行。属性允许您向属性中添加行为,同时避免 API 中的破坏性更改。
尽管属性是替代传统的 getter 和 setter 的 Pythonic 方式,但是属性也有一些实际的缺点,可以用 getter 和 setter 来克服。
**在本教程中,您已经学会了如何:**
* 用 Python 写 **getter****setter** 方法
* 使用 Python **属性**替换 getter 和 setter 方法
* 使用 Python **工具**,比如描述符,来替换 getters 和 setters
* 决定什么时候 **setter****getter** 方法可以成为作业的**正确工具**
有了这些知识,您现在可以决定何时在 Python 类中使用 getter 和 setter 方法或属性。
**源代码:** [点击这里获取免费的源代码](https://realpython.com/bonus/python-getter-setter-code/),它向您展示了如何以及何时使用 Python 中的 getters、setters 和 properties。*********