如何为Python类中的非函数成员属性创建别名?
我正在写一个Python库的API,常常遇到用户希望给同一个函数和变量起不同名字的情况。
比如说,如果我有一个Python类里面有个函数叫foo()
,我想给它起个别名叫bar()
,这非常简单:
class Dummy:
def __init__(self):
pass
def foo(self):
pass
bar = foo
现在我可以毫无问题地这样做:
d = Dummy()
d.foo()
d.bar()
我想知道的是,如何用一个类属性(比如一个字符串)来做同样的事情,而不是函数。如果我有下面这段代码:
d = Dummy()
print(d.x)
print(d.xValue)
我希望d.x
和d.xValue
总是能打印出相同的内容。如果d.x
改变了,d.xValue
也应该跟着改变(反之亦然)。
我能想到几种方法来实现这个,但都没有我想要的那么顺畅:
- 写一个自定义的注解
- 使用
@property
注解并修改设置器 - 重写
__setattr__
这个类的方法
这些方法中哪种最好?或者有没有其他的方法?我总觉得,如果给函数起别名这么简单,那么给普通变量起别名也应该一样简单……
6 个回答
当一半的用户选择使用 d.x
,而另一半选择 d.xValue
时,你打算怎么办?当他们尝试共享代码时会发生什么?当然,如果你知道所有的别名,它们是可以工作的,但这会很明显吗?当你把代码放一边整整一年后,你还会记得吗?
最后,我认为这种看似方便的做法其实是一个陷阱,最终会带来更多的困惑,而不是好处。
这主要是因为我的脚本 API 被多个子系统和领域使用,所以默认的词汇会有所不同。在一个领域中被称为“X”的东西,在另一个领域中可能被称为“Y”。
你可以通过这种方式创建属性的别名:
class Dummy(object):
def __init__(self):
self.x=1
@property
def xValue(self):
return self.x
@xValue.setter
def xValue(self,value):
self.x=value
d=Dummy()
print(d.x)
# 1
d.xValue=2
print(d.x)
# 2
但基于上述原因,我认为这不是一个好的设计。这让 Dummy 变得更难阅读、理解和使用。对于每个用户来说,你让他们需要了解的 API 变成了两倍。
一个更好的选择是使用 适配器设计模式。这样可以让 Dummy 保持简洁、紧凑:
class Dummy(object):
def __init__(self):
self.x=1
而那些希望使用不同词汇的子领域用户可以通过使用适配器类来实现:
class DummyAdaptor(object):
def __init__(self):
self.dummy=Dummy()
@property
def xValue(self):
return self.dummy.x
@xValue.setter
def xValue(self,value):
self.dummy.x=value
对于 Dummy 中的每个方法和属性,你只需连接类似的方法和属性,这些方法和属性会把复杂的工作交给 Dummy 的一个实例来处理。
虽然可能会多写几行代码,但这将帮助你保持 Dummy 的设计清晰,更容易维护、记录和单元测试。人们会写出更有意义的代码,因为这个类会限制可用的 API,并且每个概念在他们选择的类中只有一个名称。
这个问题可以用和类方法一样的方式来解决。比如:
class Dummy:
def __init__(self):
self._x = 17
@property
def x(self):
return self._x
@x.setter
def x(self, inp):
self._x = inp
@x.deleter
def x(self):
del self._x
# Alias
xValue = x
d = Dummy()
print(d.x, d.xValue)
#=> (17, 17)
d.x = 0
print(d.x, d.xValue)
#=> (0, 0)
d.xValue = 100
print(d.x, d.xValue)
#=> (100, 100)
这两个值会始终保持同步。你可以用自己喜欢的属性名称来写实际的代码,然后用你需要的旧名称来给它起个别名。
你可以提供一个 __setattr__
和 __getattr__
,它们可以用来指向一个别名映射表:
class Dummy:
aliases = {
'xValue': 'x',
'another': 'x',
}
def __init__(self):
self.x = 17
def __setattr__(self, name, value):
name = self.aliases.get(name, name)
object.__setattr__(self, name, value)
def __getattr__(self, name):
if name == "aliases":
raise AttributeError # http://nedbatchelder.com/blog/201010/surprising_getattr_recursion.html
name = self.aliases.get(name, name)
return object.__getattribute__(self, name)
d = Dummy()
assert d.x == 17
assert d.xValue == 17
d.x = 23
assert d.xValue == 23
d.xValue = 1492
assert d.x == 1492