Python中的函数组合运算符
在中,我询问了Python中的函数组合运算符。提供了执行此任务的以下代码Python中的函数组合运算符,python,python-decorators,function-composition,Python,Python Decorators,Function Composition,在中,我询问了Python中的函数组合运算符。提供了执行此任务的以下代码 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.fu
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))
通过这些添加,可以使用@
、*
和
作为运算符来组合函数。例如,可以编写打印((add1@add2)(5),(add1*add2)(5),(add1>add2)(5))
并获取#8
。(PyCharm抱怨布尔值不能调用(add1>add2)(5)
,但它仍然运行。)
但一直以来,我都想使用
作为函数合成操作符。所以我补充说
def __getattribute__(self, other):
return lambda *args, **kw: self.func(other.func(*args, **kw))
(请注意,这会弄脏update\u wrapper
,为了解决此问题,可以将其移除。)
当我运行print((add1.add2)(5))
时,我在运行时遇到以下错误:AttributeError:'str'对象没有属性“func”
。显然,\uuuuuGetAttribute\uuuuuu
的参数在传递到\uuuuuuuGetAttribute\uuuuu
之前被转换成字符串
有没有办法解决这个问题?还是我把问题误诊了,其他方法也行?你不能得到你想要的。
表示法不是二进制运算符,它是一个,只有值操作数(位于
的左侧)和标识符。是字符串,而不是生成值引用的完整表达式
从:
属性引用是主引用,后跟句点和名称:
attributeref ::= primary "." identifier
主对象必须计算为支持属性引用的类型的对象,大多数对象都会这样做。然后要求该对象生成名称为标识符的属性
因此,在编译时,Python将标识符
解析为字符串值,而不是表达式(这是操作数到运算符的结果)。\uuuu getattribute\uuu
钩子(以及任何其他钩子)只需处理字符串。这是没有办法的;动态属性访问函数严格要求name
必须是字符串:
>>> getattr(object(), 42)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: getattr(): attribute name must be string
getattr(object(),42)
回溯(最近一次呼叫最后一次):
文件“”,第1行,在
TypeError:getattr():属性名称必须是字符串
如果要使用语法组合两个对象,则只能使用二进制运算符,因此表达式只能使用两个操作数,并且只能使用具有挂钩的表达式(布尔值
和和或操作符没有挂钩,因为它们的计算很慢,是和不是没有挂钩,因为它们操作对象标识,而不是对象值)。我实际上不愿意提供这个答案。但是你应该知道,在某些情况下,你可以使用点“
”表示法,即使它是主要的。此解决方案仅适用于可以从全局()访问的函数。
:
要测试:
@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
更改为\u func
,以防止与实际名为func
的函数组合时发生冲突。此外,我将lambda包装在一个可组合(…)
中,以便它本身可以组合
证明其在全球范围外工作:
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
中的other
是属性的名称,它不是像
或@
那样的二进制运算符。正如您所看到的,
是为访问属性和方法保留的。但是您可以使用与原始运算符更相似的°
,并且不会产生此问题。@FrenchMasterSword实现它的神奇方法是什么?!它不是一个有效的Python操作符。它不是一个Python操作符^^^'@Frenchmaster剑所以…这到底是一个有用的建议吗?你是在建议OP编写自己版本的解释器来作为操作符处理该符号吗?我一直在试图找到绕过此限制的方法,但是我想我必须放弃。@
和*
并没有那么糟糕。我在这里问了一个相关的问题:非常奇怪。但是非常感谢您不辞辛劳地提供这些信息。这只适用于中定义的模块中的全局变量。Composable
。然后您可以开始从堆栈中提取名称,这仍然不能给您完整的表达式访问权限;这只是语法错误或按错误的顺序进行分析。@Russabbot不确定您为什么认为这很奇怪?您有一个字符串,并且globals()
是模块名称空间映射字符串到对象的字典。因此,是的,如果您将自己限制在同一模块中的全局变量,则可以通过这种方式将属性名称映射到全局变量。这是一种间接路径,只是不要期望与二进制运算符挂钩具有相同的表达自由度。@Martijn Pieters。您是对的。”“怪异”是一个错误的词。我的反应是,这个解决方案只在有限的情况下有效——正如你也指出的。
@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