Python 中的函数组合运算符

Rus*_*ott 7 python function-composition python-decorators

这个问题中,我询问了Python中的函数组合运算符。@Philip Tzou提供了以下代码,它可以完成这项工作。

import functools

class Composable:

    def __init__(self, func):
        self.func = func
        functools.update_wrapper(self, func)

    def __matmul__(self, other):
        return lambda *args, **kw: self.func(other.func(*args, **kw))

    def __call__(self, *args, **kw):
        return self.func(*args, **kw)
Run Code Online (Sandbox Code Playgroud)

我添加了以下功能。

def __mul__(self, other):
    return lambda *args, **kw: self.func(other.func(*args, **kw))

def __gt__(self, other):
    return lambda *args, **kw: self.func(other.func(*args, **kw))
Run Code Online (Sandbox Code Playgroud)

通过这些添加,我们可以使用@*>as 运算符来组成函数。例如,我们可以编写print((add1 @ add2)(5), (add1 * add2)(5), (add1 > add2)(5))并获取# 8 8 8. (PyCharm 抱怨布尔值不可调用(add1 > add2)(5)。但它仍然运行。)

不过,我一直想将其.用作函数组合运算符。所以我添加了

def __getattribute__(self, other):
    return lambda *args, **kw: self.func(other.func(*args, **kw))
Run Code Online (Sandbox Code Playgroud)

(请注意,这会弄乱update_wrapper,为了这个问题可以将其删除。)

当我运行时,print((add1 . add2)(5))我在运行时收到此错误:AttributeError: 'str' object has no attribute 'func'。事实证明(显然)参数 to__getattribute__在传递给 之前被转换为字符串__getattribute__

有没有办法绕过这种转换?或者我是否误诊了问题,而其他方法会起作用?

Mar*_*ers 6

你无法拥有你想要的。该.表示法不是二元运算符,它是一个主运算符,仅包含值操作数( 的左侧).标识符标识符是字符串,而不是生成对值的引用的完整表达式。

属性参考部分

属性引用是主要的,后跟句点和名称:

attributeref ::=  primary "." identifier
Run Code Online (Sandbox Code Playgroud)

主对象必须求值为支持属性引用的类型的对象,大多数对象都支持属性引用。然后要求该对象生成名称为标识符的属性。

因此,在编译时,Python 会解析identifier为字符串值,而不是表达式(这是运算符的操作数得到的表达式)。该__getattribute__钩子(以及任何其他属性访问钩子)只需处理字符串。这是没有办法解决的;动态属性访问函数getattr()严格强制name必须是字符串:

>>> getattr(object(), 42)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: getattr(): attribute name must be string
Run Code Online (Sandbox Code Playgroud)

如果要使用语法来组合两个对象,则仅限于二元运算符,因此需要两个操作数的表达式,并且只有那些具有钩子的表达式(布尔值andor运算符没有钩子,因为它们计算延迟,is并且is not没有钩子因为它们对对象标识进行操作,而不是对对象值进行操作)。


Phi*_*zou 5

我其实不愿意提供这个答案。但您应该知道在某些情况下您可以使用点“ .”符号,即使它是主要的。该解决方案仅适用于可以从以下位置访问的功能globals()

import functools

class Composable:

    def __init__(self, func):
        self.func = func
        functools.update_wrapper(self, func)

    def __getattr__(self, othername):
        other = globals()[othername]
        return lambda *args, **kw: self.func(other.func(*args, **kw))

    def __call__(self, *args, **kw):
        return self.func(*args, **kw)

Run Code Online (Sandbox Code Playgroud)

去测试:

@Composable
def add1(x):
    return x + 1

@Composable
def add2(x):
    return x + 2

print((add1.add2)(5))
# 8
Run Code Online (Sandbox Code Playgroud)