自定义如何将Python对象作为函数参数处理?

时间:2018-03-21 22:45:34

标签: python

Python类的__call__方法允许我们指定类成员应该如何表现为函数。我们可以做“对立”,即指定一个类成员应该如何作为任意其他函数的参数?

作为一个简单的例子,假设我有一个包含列表的ListWrapper类,当我在这个类的成员上调用函数f时,我希望映射f在包装清单上。例如:

x = WrappedList([1, 2, 3])
print(x + 1) # should be WrappedList([2, 3, 4])

d = {1: "a", 2: "b", 3:"c"}
print(d[x]) # should be WrappedList(["a", "b", "c"])

调用假设的__call__类似物我正在寻找__arg__,我们可以想象这样的事情:

class WrappedList(object):
    def __init__(self, to_wrap):
        self.wrapped = to_wrap

   def __arg__(self, func):
       return WrappedList(map(func, self.wrapped))

现在,我知道(1)__arg__在这种形式中不存在,(2)在没有任何技巧的情况下,很容易在这个简单的例子中得到行为。但有没有办法近似我在一般情况下寻找的行为?

1 个答案:

答案 0 :(得分:4)

你不能这样做。 *

可以为大多数内置运算符(比如你的+示例)和一些内置函数(如abs)执行等效操作。它们是通过调用操作数上的特殊方法来实现的,如the reference docs中所述。

当然,这意味着为每个类型编写一大堆特殊方法 - 但是编写实现的基类(或装饰器或元类,如果这不适合您的设计)并不难通过调用子类的__arg__然后执行默认操作,在一个地方使用所有这些特殊方法:

class ArgyBase:
    def __add__(self, other):
        return self.__arg__() + other
    def __radd__(self, other):
        return other + self.__arg__()
    # ... and so on

如果你想将它扩展到你自己创建的一整套函数,你可以给它们所有类似于内置函数的类似特殊方法协议,并将你的基类扩展为掩盖他们。或者您可以将其短路并直接在这些功能中使用__arg__协议。为了避免大量重复,我会使用装饰器。

def argify(func):
    def _arg(arg):
        try:
            return arg.__arg__()
        except AttributeError:
            return arg
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        args = map(_arg, args)
        kwargs = {kw: _arg(arg) for arg in args}
        return func(*args, **kwargs)
    return wrapper

@argify
def spam(a, b):
    return a + 2 * b

如果你真的想,你可以绕过其他人的功能:

sin = argify(math.sin)

......甚至是monkeypatching他们的模块:

requests.get = argify(requests.get)

...或者在早期版本的gevent中动态地修改整个模块,但是我甚至不会证明这一点,因为此时我们已经进入了不做这个 - 多原因领域。

您在评论中提到,如果可能的话,您不必事先指定一些其他人的功能。这是否意味着您导入的任何模块中构造的每个函数都是?好吧,如果你愿意创建一个导入钩子,你甚至可以 ,但这似乎是一个更糟糕的主意。解释如何编写导入钩子以及AST-patch每个函数创建节点或在字节码周围插入包装器等都是太多了,但如果你的研究能力超出你的常识,你就可以搞清楚。 :)

作为旁注,如果我这样做,我就不会调用方法__arg__,我会将其称为arg_arg。除了为语言的未来使用而保留之外,dunder-method风格暗示了这里不正确的事情(特殊方法查找而不是普通调用,您可以在文档中搜索它等)。

*你可以使用多种语言,例如C ++,其中隐式转换和类型化变量的组合而不是类型化值意味着你可以通过给它们一个奇怪的类型来获得一个对象调用的方法。隐式转换运算符到期望的类型。