在this question中,我询问了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__
之前已转换为字符串。
有没有办法解决这种转换?还是我误诊了问题,还有其他方法可行吗?
答案 0 :(得分:3)
您没有想要的东西。 .
表示不是二进制运算符,它是primary,只带有值操作数(.
的左侧),并且标识符。 Identifiers是字符串,不是生成对值的引用的成熟表达式。
来自Attribute references section:
属性引用是主引用,后跟句点和名称:
attributeref ::= primary "." identifier
主要对象必须对支持属性引用的类型的对象求值,大多数对象都会这样做。然后要求该对象产生名称为标识符的属性。
因此,在编译时,Python将identifier
解析为一个字符串值,而不是一个表达式(这是为运算符提供给运算符的结果)。 __getattribute__
钩子(和任何其他attribute access hooks)仅需处理字符串。没有办法解决这个问题。动态属性访问函数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
没有钩子,因为它们是根据对象标识而不是对象值进行操作的。)
答案 1 :(得分:0)
我实际上不愿意提供这个答案。但是您应该知道,在某些情况下,即使它是主要的,也可以使用点“ .
”。此解决方案仅适用于可以从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
答案 2 :(得分:0)
您可以使用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
更改为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