[英]A function composition operator in Python
在這個問題中,我詢問了 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,后跟一個句點和一個名稱:
attributeref ::= primary "." identifier
主要對象必須評估為支持屬性引用的類型的對象,大多數對象都這樣做。 然后要求該對象生成名稱為標識符的屬性。
因此,在編譯時,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
如果你想使用語法來組合兩個對象,你只能使用二元運算符,所以需要兩個操作數的表達式,並且只有那些有鈎子的表達式(布爾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
更改為_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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.