在python中是否有可扩展魔术方法的最佳实践?

时间:2017-03-08 08:25:13

标签: python magic-methods

假设我已经构建了一个包含Foo类的库,并支持一些神奇的方法,比如__add__()__radd__()

>>> class Foo(object):
...     def __add__(self, rhs):
...         print("Foo.__add__", rhs)
...     def __radd__(self, lhs):
...         print("Foo.__radd__", lhs)
... 
>>> foo = Foo()
>>> foo + 3
Foo.__add__ 3
>>> 3 + foo
Foo.__radd__ 3

在计算3 + foo时,python首先调用type(3).__add__(3, foo),但当它返回NotImplemented时,它会回退到type(foo).__radd__(foo, 3)

>>> type(3).__add__(3, foo)
NotImplemented

我希望开发人员能够在我的库之上构建库,比如一个包含类Bar的库,我希望它们具有完全控制权。特别是,我想实现一些机制,让其他库决定foo + bar是应该调用foo.__add__(bar)还是bar.__radd__(foo)

我看到NumPy使用__array_priority__方案解决了这个问题。但这似乎引起了一些令人头疼的问题(考虑到问题和问题的数量)。还有其他最佳做法吗?

3 个答案:

答案 0 :(得分:1)

一个流行的选择是维护LHS支持的类型列表,如果RHS的类型不在列表中,则返回NotImplemented

class Foo(object):
    SUPPORTED_TYPES = (int, Foo)
    def __add__(self, rhs):
        if isinstance(type(rhs), SUPPORTED_TYPES):
            [...] # compute self + rhs
        else:
            return NotImplemented

除非rhsSUPPORTED_TYPES之一的智能子类型,否则这很有效:它无法获得控制权。 而且,这种列表类型不是很灵活。依赖鸭子打字可能比在硬编码类型列表上更好。

答案 1 :(得分:1)

一个简单的选择是尝试让LHS做它需要做的任何事情(在下面的示例中它调用RHS的value()方法)并且如果它引发异常,捕获它并返回{{1 }}:

NotImplemented

尽可能简单,无需维护class Foo(object): [...] def __add__(self, rhs): try: return self._value + rhs.value() except AttributeError: return NotImplemented 列表。但是,RHS可能会实现与此任务无关的SUPPORTED_TYPES方法,因此可能存在风险。此外,value()没有简单的方法可以完全控制结果。

在Python中,通常最好是请求宽恕而不是如上所述请求许可,但您可能更愿意检查rhs是否有rhs方法:

value()

答案 2 :(得分:0)

另一种选择是使用__foo_priority__之类的属性,有点像NumPy对其__array_priority__所做的那样:

class Foo(object):
    __foo_priority__ = 0
    def __add__(self, rhs):
        delegate = True
        try:
            rhs_prio = type(rhs).__foo_priority__
            delegate = (self.__foo_priority__ < rhs_prio)
        except AttributeError:
            delegate = True
        if delegate:
            return NotImplemented
        else:
            return self.value_ + rhs.value()

这个选项稍微复杂一点,但非常灵活。此选项的唯一(次要)问题是它要求rhs类型具有额外属性,因此如果rhs无法控制,则无法控制#!/usr/bin/python import pandas as pd file = 'sample.xls' df = pd.read_excel(file, sheetname=0, skiprows=7) 它是没有此属性的现有类型。