当处理不断发展的API时,在一个类中重命名一个属性可能是有用的,但为了向后兼容而保留旧的名字。 这意味着使一个属性成为另一个属性的别名。 在这篇文章中,我们将看一下实现这一目的的两种方法。
使用@property
实现这一目标的一个方法是用 @property例如,这里有一个Widget 类,其中cycles 是rotations 的别名:
def __init__(self, rotations: int) -> None:
self.rotations = rotations
@property
def cycles(self) -> int:
return self.rotations
@cycles.setter
def cycles(self, value: int) -> None:
self.rotations = value
用以下方法检查这个 ipython -i example.py:
In [1]: widget = Widget(1337)
In [2]: widget.cycles
Out[2]: 1337
In [3]: widget.cycles = 9001
In [4]: widget.cycles
Out[4]: 9001
In [5]: widget.rotations
Out[5]: 9001
好了,这就完成了任务,我们可以在这里结束这篇文章......但我们不
(一个完全透明的别名也会有一个删除方法来处理很少使用的del 语句)。
这种方法的缺点是它的重复性。 如果我们想要很多别名,我们必须为每个别名写几行类似的属性函数。 这感觉就像我们被困在Java土地上,浪费了我们写简单的getters和setters的时间。 然后如果我们想在测试中获得100%的覆盖率,我们必须独立测试每个方法。
Python 是否提供了一种机制来避免这种抄袭? 是的,是的,它提供了。
进入描述符
我们可以使用描述符来实现别名。 描述符是一种特殊的协议,可以在属性访问过程中运行额外的处理。 这通过三个特殊的方法使用。
__get__拦截访问__set__拦截分配__delete__拦截删除
即使你没有创建过描述符,如果你已经使用了一段时间的Python,你肯定已经使用了一个。 我们甚至在上面使用了一个。描述符协议为@property ,以及@classmethod ,@cached_property ,以及更多。
我们可以像这样创建一个别名描述符类:
def __init__(self, source_name):
self.source_name = source_name
def __get__(self, obj, objtype=None):
if obj is None:
# Class lookup, return descriptor
return self
return getattr(obj, self.source_name)
def __set__(self, obj, value):
setattr(obj, self.source_name, value)
source_name 是要别名的属性名称,我们把它存储在描述符实例中。我们的 和 方法然后代理获取/设置实例上的底层属性 ( )。 也可以在类上调用,在这种情况下,我们通常返回描述符。__get__ __set__``obj``__get__
(同样,为了完整起见,我们也会通过添加一个__delete__ 方法来处理属性的删除)。
我们可以像这样使用我们的描述符:
def __init__(self, rotations: int) -> None:
self.rotations = rotations
cycles = Alias("rotations")
turns = Alias("rotations")
cycles 和turns 中的每一个都是我们的描述符类的一个实例。当触摸Widget 类上的别名属性或其实例时,Python 看到它们是描述符并运行适当的方法。
在操作中,这看起来与上面的情况类似:
In [2]: widget.turns
Out[2]: 1024
In [3]: widget.cycles
Out[3]: 1024
In [4]: widget.turns = 2048
In [5]: widget.rotations
Out[5]: 2048
In [6]: widget.cycles
Out[6]: 2048
In [7]: widget.turns
Out[7]: 2048
看起来很合法!
这种方法为我们节省了很多行代码。而且我们可以单独测试我们的Alias 类一次,而不是为每个别名写很多测试。
但也有一个缺点。
精明的读者会注意到我们在每个例子中都使用了类型提示,除了我们的Alias 类。这是因为为我们的描述符编写正确的类型提示是......非同小可的。 它需要几个高级类型特征。Generic,TypeVar, 和@overload ,导致更多的代码行。
如果你使用类型提示,你可能更愿意坚持使用verbose@property 方法,因为类型检查器很容易识别和验证这些类型。