繁体   English   中英

做组合别名的 Pythonic 方式

[英]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

第一种方法有很多问题:

  1. 除非您跳过额外的环节,否则当它引用的属性发生变化时,别名不会更新。 例如,您可以使用 setter 使house成为property ,但对于不需要它的东西来说,这是一项重要的工作。 有关示例实现,请参阅此答案的结尾。
  2. cleanup_house将不可继承。 在类中定义的函数对象是一个非数据描述符,可以被继承和覆盖,也可以绑定到一个实例。 类中根本不存在第一种方法中的实例属性。 它是一个绑定方法的事实是偶然的。 对于一个具体的例子,子类将无法访问super().cleanup_house
  3. 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM