簡體   English   中英

Python 中的函數組合運算符

[英]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

如果你想使用語法來組合兩個對象,你只能使用二元運算符,所以需要兩個操作數的表達式,並且只有那些有鈎子的表達式(布爾andor運算符沒有鈎子,因為它們懶惰地求值, isis 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM