通过描述符管理属性
Python 描述符是允许您对给定自定义类的属性进行精细控制的类。您可以使用 Descriptors 在给定类的属性之上添加类似函数的行为。
描述符类至少需要 .__get__()
特殊方法。但是,完整描述符协议由以下方法组成:
方法 | 描述 |
---|---|
__get__(self, instance, type=None) | Getter 方法,该方法允许您检索 managed 属性的当前值 |
__set__(self, instance, value) | 允许您为 managed 属性设置新值的 Setter 方法 |
__delete__(self, instance) | Deleter 方法,该方法允许您从包含类中删除 managed 属性 |
__set_name__(self, owner, name) | 名称 setter 方法,用于定义托管属性的名称 |
为了说明 Descriptors 的用途,假设您有一个 shapes.py 模块。您已经定义了 Circle、Square 和 Rectangle 类。您已经意识到需要验证圆的半径、正方形的边等参数。您还注意到,验证逻辑对于所有这些参数都是通用的。
在这种情况下,您可以使用描述符来管理验证逻辑,其中包括检查提供的值是否为正数。以下是此 Descriptors 的可能实现:
class PositiveNumber:
def __set_name__(self, owner, name):
self._name = name
def __get__(self, instance, owner):
return instance.__dict__[self._name]
def __set__(self, instance, value):
if not isinstance(value, int | float) or value <= 0:
raise ValueError("positive number expected")
instance.__dict__[self._name] = value
在此类中,首先定义 .__set_name__()
特殊方法。owner 参数表示包含类,而 name 参数表示属性名称。
然后,定义 .__get__()
方法。此方法采用一个实例和对包含类的引用。在此方法中,使用 .dict 特殊属性来访问实例的命名空间并检索 managed 属性的值。
最后,定义 .__set__()
方法,该方法采用包含类的实例和 managed 属性的新值。在此方法中,您将检查提供的值是否为正数,在这种情况下,您将引发 ValueError 异常。否则,您将再次使用 .__dict__
命名空间将 input 值分配给 managed 属性。
现在,您可以使用此描述符来定义形状的参数:
import math
# ...
class Circle:
radius = PositiveNumber()
def __init__(self, radius):
self.radius = radius
def area(self):
return round(math.pi * self.radius**2, 2)
class Square:
side = PositiveNumber()
def __init__(self, side):
self.side = side
def area(self):
return round(self.side**2, 2)
class Rectangle:
height = PositiveNumber()
width = PositiveNumber()
def __init__(self, height, width):
self.height = height
self.width = width
def area(self):
return self.height * self.width
在此代码中,突出显示的行显示了如何使用 PositiveNumber 描述符创建托管属性。请注意,托管属性必须是具有实例属性对应项的类属性。
以下是Shape使用方式:
>>> from shapes import Circle, Square, Rectangle
>>> circle = Circle(-10)
Traceback (most recent call last):
...
ValueError: positive number expected
>>> square = Square(-20)
Traceback (most recent call last):
...
ValueError: positive number expected
>>> rectangle = Rectangle(-20, 30)
Traceback (most recent call last):
...
ValueError: positive number expected
所有形状都会在实例化时验证输入参数。这是因为他们都使用你的 PositiveNumber 来管理他们的参数。这样,你就可以在所有类中重复使用验证代码。
使用 Iterators 和 Iterables 支持迭代
在 Python 中,迭代器和可迭代对象是两种不同但相关的工具,当您需要迭代数据流或容器时,它们可以派上用场。Iterators 支持并控制迭代过程,而 iterables 通常保存您可以迭代的数据。
迭代器和可迭代对象是 Python 编程中的基本组件。您将在几乎所有程序中直接或间接使用它们。在以下部分中,您将学习如何使用特殊方法将自定义类转换为迭代器和可迭代对象的基础知识。
Creating Iterators 创建迭代器
要在 Python 中创建迭代器,您需要两个特殊方法。通过实现这些方法,您将控制迭代过程。您可以定义在 for 循环或其他迭代构造中使用类时 Python 如何检索项。
下表显示了组成迭代器的方法。它们被称为迭代器协议:
方法 | 描述 |
---|---|
__iter__() | 调用以初始化迭代器。它必须返回一个 iterator 对象。 |
__next__() | 调用以迭代迭代器。它必须返回数据流中的下一个值。 |
这两种方法都有自己的责任。在 .__iter__()
中,您通常返回当前对象 self。在 .__next__()
中,您需要返回序列中数据流中的下一个值。当数据流耗尽时,此方法必须引发 StopIteration。这样,Python 就知道迭代已经结束。
通过在自定义类中实现这两个方法,可以将它们转换为迭代器。例如,假设您要创建一个类,该类为斐波那契数列中的数字提供迭代器。在这种情况下,您可以编写以下解决方案:
class FibonacciIterator:
def __init__(self, stop=10):
self._stop = stop
self._index = 0
self._current = 0
self._next = 1
def __iter__(self):
return self
def __next__(self):
if self._index < self._stop:
self._index += 1
fib_number = self._current
self._current, self._next = (
self._next,
self._current + self._next,
)
return fib_number
else:
raise StopIteration
在这个 FibonacciIterator 类中,您首先初始化一些 attributes。._stop 属性定义类将返回的值的数量。默认为 10。._index 属性保存序列中当前斐波那契值的索引。._current 和 ._next 属性分别表示斐波那契数列中的当前值和下一个值。
在 .__iter__()
中,你只需返回当前对象 self。在 Python 中创建迭代器时,这种做法很常见。
.__next__()
方法要复杂一些。在此方法中,您有一个条件,用于检查当前序列索引是否未达到 ._stop 值,在这种情况下,您可以递增当前索引以控制迭代过程。然后,您计算与当前索引对应的斐波那契数,并将结果返回给 .__next__()
的调用方。
当 ._index 增长到 ._stop 的值时,将引发 StopIteration 异常,这将终止迭代过程。
您的类现在已准备好进行迭代。以下是它的使用:
>>> from fib_iter import FibonacciIterator
>>> for fib_number in FibonacciIterator():
... print(fib_number)
...
0
1
1
2
3
5
8
13
21
34
FibonacciIterator 类实时计算新值,当您在循环中使用该类时,会按需生成值。当斐波那契值的数量增长到 10 时,该类将引发 StopIteration 异常,并且 Python 会终止循环。
构建可迭代对象
Iterables 与 iterators 略有不同。通常,当集合或容器实现 .__iter__()
特殊方法时,它是可迭代的。
例如,Python 内置容器类型(如列表、元组、字典和集)是可迭代对象。它们提供您可以迭代的数据流。但是,它们不提供 .__next__()
方法,该方法驱动迭代过程。因此,要使用 for 循环迭代可迭代对象,Python 会隐式创建一个迭代器。
例如,返回到 Stack 类。使用以下代码更新类以使其可迭代:
class Stack:
# ...
def __iter__(self):
return iter(self.items[::-1])
请注意,.__iter__()
的这个实现不会返回当前对象 self。相反,该方法使用内置的 iter() 函数在堆栈中的项目上返回一个迭代器。iter() 函数为输入可迭代对象提供默认的 .__next__()
方法,使其成为一个成熟的迭代器。
在迭代之前反转堆栈中的项列表,以与元素从堆栈中弹出的顺序保持一致。在 Python 交互式会话中运行以下代码以试用 Stack 的这项新功能:
>>> from stack import Stack
>>> stack = Stack([1, 2, 3, 4])
>>> for value in stack:
... print(value)
...
4
3
2
1
在此代码段中,您首先创建一个包含四个数字的 Stack 新实例。然后,在实例上启动 for 循环。循环在每次迭代中打印当前值。这表明如果你重复调用 .pop() ,顺序元素将被弹出。您的 Stack 类现在支持迭代。