[英]Pythonic way of doing composition aliases
做组合别名的最pythonic和正确的方法是什么?
这是一个假设的场景:
class House:
def cleanup(self, arg1, arg2, kwarg1=False):
# do something
class Person:
def __init__(self, house):
self.house = house
# aliases house.cleanup
# 1.
self.cleanup_house = self.house.cleanup
# 2.
def cleanup_house(self, arg1, arg2, kwarg1=False):
return self.house.cleanup(arg1=arg1, arg2=arg2, kwarg1=kwarg1)
AFAIK 与 #1 我测试的编辑器理解这些和 #2 一样好 - 自动完成、文档字符串等。
#1 方法有什么缺点吗? 从python的角度来看,哪种方式更正确?
扩展方法 #1 unsettable 和类型提示的变体将不受评论中指出的所有问题的影响:
class House:
def cleanup(self, arg1, arg2, kwarg1=False):
"""clean house is nice to live in!"""
pass
class Person:
def __init__(self, house: House):
self._house = house
# aliases
self.cleanup_house = self.house.cleanup
@property
def house(self):
return self._house
第一种方法有很多问题:
house
成为property
,但对于不需要它的东西来说,这是一项重要的工作。 有关示例实现,请参阅此答案的结尾。cleanup_house
将不可继承。 在类中定义的函数对象是一个非数据描述符,可以被继承和覆盖,也可以绑定到一个实例。 类中根本不存在第一种方法中的实例属性。 它是一个绑定方法的事实是偶然的。 对于一个具体的例子,子类将无法访问super().cleanup_house
。person.cleanup_house.__name__ != 'cleanup_house'
。 这不是您经常检查的内容,但是当您这样做时,您会期望函数名称为cleanup
。 好消息是您不必多次重复签名即可使用方法#2。 Python 提供了非常方便的 splat ( *
)/splatty-splat ( **
) 表示法,用于将所有参数检查委托给被包装的方法:
def cleanup_house(self, *args, **kwargs):
return self.house.cleanup(*args, **kwargs)
就是这样。 所有常规和默认参数都按原样传递。
这就是 #2 是迄今为止更 Pythonic 的方法的原因。 除非您复制方法签名,否则我不知道它将如何与支持类型提示的编辑器交互。
可能有问题的一件事是cleanup_house.__doc__
与house.cleanup.__doc__
。 这可能值得将house
转换为property
,其 setter 分配cleanup_house.__doc__
。
要解决问题 1.(但不是 2. 或 3.),您可以使用 setter 将house
实现为属性。 这个想法是在house
属性发生变化时更新别名。 一般来说,这不是一个好主意,但这里是您在问题中所拥有的替代实现,它可能会更好地工作:
class House:
def cleanup(self, arg1, arg2, kwarg1=False):
"""clean house is nice to live in!"""
pass
class Person:
def __init__(self, house: House):
self.house = house # use the property here
@property
def house(self):
return self._house
@house.setter
def house(self, value):
self._house = house
self.cleanup_house = self._house.cleanup
我只是想在这里添加另一种方法,如果您希望house
是公开且可设置的(我通常会将这样的事情视为不可变的),您可以将cleanup_house
设为属性,如下所示:
class House:
def cleanup(self, arg1, arg2, kwarg1=False):
"""clean house is nice to live in!"""
print('cleaning house')
class Person:
def __init__(self, house: House):
self.house = house
@property
def cleanup_house(self):
return self.house.cleanup
至少在 Ipython REPL 中,代码完成和文档字符串似乎如您所愿。 请注意它如何与类型注释交互...
编辑:所以, mypy 0.740 至少不能推断person.cleanup_house
的类型签名,所以这不是很好,虽然,这并不奇怪:
(py38) Juans-MBP:workspace juan$ cat test_mypy.py
class House:
def cleanup(self, arg1:int, arg2:bool):
"""clean house is nice to live in!"""
print('cleaning house')
class Person:
house: House
def __init__(self, house: House):
self.house = house # use the property here
@property
def cleanup_house(self):
return self.house.cleanup
person = Person(House())
person.cleanup_house(1, True)
person.cleanup_house('Foo', 'Bar')
reveal_type(person.cleanup_house)
reveal_type(person.house.cleanup)
(py38) Juans-MBP:workspace juan$ mypy test_mypy.py
test_mypy.py:19: note: Revealed type is 'Any'
test_mypy.py:20: note: Revealed type is 'def (arg1: builtins.int, arg2: builtins.bool) -> Any'
我仍然会选择#2。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.