Python 中的函数组合运算符
A function composition operator in Python
在 I asked about a function composition operator in 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)
我添加了以下功能。
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))
通过这些添加,可以使用 @
、*
和 >
作为运算符来组合函数。例如,可以写 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))
(请注意,这会弄脏 update_wrapper
,为了这个问题可以将其删除。)
当我 运行 print((add1 . add2)(5))
时,我在 运行 时收到此错误:AttributeError: 'str' object has no attribute 'func'
。事实证明(显然)__getattribute__
的参数在传递给 __getattribute__
之前被转换为字符串。
是否有解决该转换的方法?还是我误诊了问题,其他一些方法会起作用吗?
你得不到你想要的。 .
表示法 不是二元运算符 ,它是 primary, with only the value operand (the left-hand side of the .
), and an identifier. Identifiers 字符串,而不是生成值引用的完整表达式。
来自Attribute references section:
An attribute reference is a primary followed by a period and a name:
attributeref ::= primary "." identifier
The primary must evaluate to an object of a type that supports attribute references, which most objects do. This object is then asked to produce the attribute whose name is the identifier.
因此在编译时,Python 将 identifier
解析为字符串值,而不是表达式(这是运算符的操作数)。 __getattribute__
钩子(以及任何其他 attribute access hooks) only has to deal with strings. There is no way around this; the dynamic attribute access function getattr()
钩子严格强制 name
必须是字符串:
>>> getattr(object(), 42)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: getattr(): attribute name must be string
如果你想使用语法来组合两个对象,你只能使用二元运算符,所以表达式需要两个操作数,并且只有那些有钩子的(布尔and
和 or
运算符没有钩子,因为它们延迟计算,is
和 is not
没有钩子,因为它们对对象标识而不是对象值进行操作)。
这个答案我其实是不愿意的。但是你应该知道在某些情况下你可以使用点“.
”符号,即使它是主要的。此解决方案仅适用于可从 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)
测试:
@Composable
def add1(x):
return x + 1
@Composable
def add2(x):
return x + 2
print((add1.add2)(5))
# 8
您可以使用 inspect
模块绕过在全局范围内专门定义可组合函数的限制。请注意,这与您所能获得的 Pythonic 差不多,使用 inspect
将使您的代码更难跟踪和调试。这个想法是使用 inspect.stack()
从调用上下文中获取命名空间,并在那里查找变量名。
import functools
import inspect
class Composable:
def __init__(self, func):
self._func = func
functools.update_wrapper(self, func)
def __getattr__(self, othername):
stack = inspect.stack()[1][0]
other = stack.f_locals[othername]
return Composable(lambda *args, **kw: self._func(other._func(*args, **kw)))
def __call__(self, *args, **kw):
return self._func(*args, **kw)
请注意,我将 func
更改为 _func
到 half-prevent 冲突,如果您使用实际命名为 func
的函数进行组合。此外,我将您的 lambda 包装在 Composable(...)
中,以便它本身可以组合。
证明它在全局范围之外工作:
def main():
@Composable
def add1(x):
return x + 1
@Composable
def add2(x):
return x + 2
print((add1.add2)(5))
main()
# 8
这为您提供了能够将函数作为参数传递的隐含好处,而无需担心将变量名称解析为全局范围内的实际函数名称。示例:
@Composable
def inc(x):
return x + 1
def repeat(func, count):
result = func
for i in range(count-1):
result = result.func
return result
print(repeat(inc, 6)(5))
# 11
在
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)
我添加了以下功能。
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))
通过这些添加,可以使用 @
、*
和 >
作为运算符来组合函数。例如,可以写 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))
(请注意,这会弄脏 update_wrapper
,为了这个问题可以将其删除。)
当我 运行 print((add1 . add2)(5))
时,我在 运行 时收到此错误:AttributeError: 'str' object has no attribute 'func'
。事实证明(显然)__getattribute__
的参数在传递给 __getattribute__
之前被转换为字符串。
是否有解决该转换的方法?还是我误诊了问题,其他一些方法会起作用吗?
你得不到你想要的。 .
表示法 不是二元运算符 ,它是 primary, with only the value operand (the left-hand side of the .
), and an identifier. Identifiers 字符串,而不是生成值引用的完整表达式。
来自Attribute references section:
An attribute reference is a primary followed by a period and a name:
attributeref ::= primary "." identifier
The primary must evaluate to an object of a type that supports attribute references, which most objects do. This object is then asked to produce the attribute whose name is the identifier.
因此在编译时,Python 将 identifier
解析为字符串值,而不是表达式(这是运算符的操作数)。 __getattribute__
钩子(以及任何其他 attribute access hooks) only has to deal with strings. There is no way around this; the dynamic attribute access function getattr()
钩子严格强制 name
必须是字符串:
>>> getattr(object(), 42)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: getattr(): attribute name must be string
如果你想使用语法来组合两个对象,你只能使用二元运算符,所以表达式需要两个操作数,并且只有那些有钩子的(布尔and
和 or
运算符没有钩子,因为它们延迟计算,is
和 is not
没有钩子,因为它们对对象标识而不是对象值进行操作)。
这个答案我其实是不愿意的。但是你应该知道在某些情况下你可以使用点“.
”符号,即使它是主要的。此解决方案仅适用于可从 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)
测试:
@Composable
def add1(x):
return x + 1
@Composable
def add2(x):
return x + 2
print((add1.add2)(5))
# 8
您可以使用 inspect
模块绕过在全局范围内专门定义可组合函数的限制。请注意,这与您所能获得的 Pythonic 差不多,使用 inspect
将使您的代码更难跟踪和调试。这个想法是使用 inspect.stack()
从调用上下文中获取命名空间,并在那里查找变量名。
import functools
import inspect
class Composable:
def __init__(self, func):
self._func = func
functools.update_wrapper(self, func)
def __getattr__(self, othername):
stack = inspect.stack()[1][0]
other = stack.f_locals[othername]
return Composable(lambda *args, **kw: self._func(other._func(*args, **kw)))
def __call__(self, *args, **kw):
return self._func(*args, **kw)
请注意,我将 func
更改为 _func
到 half-prevent 冲突,如果您使用实际命名为 func
的函数进行组合。此外,我将您的 lambda 包装在 Composable(...)
中,以便它本身可以组合。
证明它在全局范围之外工作:
def main():
@Composable
def add1(x):
return x + 1
@Composable
def add2(x):
return x + 2
print((add1.add2)(5))
main()
# 8
这为您提供了能够将函数作为参数传递的隐含好处,而无需担心将变量名称解析为全局范围内的实际函数名称。示例:
@Composable
def inc(x):
return x + 1
def repeat(func, count):
result = func
for i in range(count-1):
result = result.func
return result
print(repeat(inc, 6)(5))
# 11