Function Composition in Python

        今天在看functools中的 reduce時看到一篇很好的blog,這篇blog對這個函數的解釋很好,尤其實在多個函數嵌套的使用中,但個人發現還是有些地方不是特別明瞭也有點小瑕疵,下面我做的補充和說明用紅色字體表示出來,方便查看。原文雖然是英文的,但沒有特別複雜的語句和單詞,因此有點基礎都能讀懂,不足翻譯了。

原文:https://mathieularose.com/function-composition-in-python/

Function Composition in Python

 January 2013 in ARTICLES

While I don’t consider myself a functional programming guru, all those hours spent in Haskell, Lisp and Scheme definitively changed my way of programming. So, after seeing a lot of unnecessarily complex implementations of function composition in Python on the Web, I decided to write this article to present a simple yet powerful solution that covers all use cases. If you are familiar with function composition, you may want to go to the solution.

Composing two functions

Function composition is a way of combining functions such that the result of each function is passed as the argument of the next function. For example, the composition of two functions f and g is denoted f(g(x))x is the argument of g, the result of g is passed as the argument of f and the result of the composition is the result of f.

Let’s define compose2, a function that takes two functions as arguments (fand g) and returns a function representing their composition:

def compose2(f, g):
    return lambda x: f(g(x))

Example:

>>> def double(x):
...     return x * 2
...
>>> def inc(x):
...     return x + 1
...
>>> inc_and_double = compose2(double, inc)
>>> inc_and_double(10)
22

Composing n functions

Now that we know how to compose two functions, it would be interesting to generalize it to accept n functions. Since the solution is based on compose2, let’s first look at the composition of three functions using compose2.

>>> def dec(x):
...     return x - 1
...
>>> inc_double_and_dec = compose2(compose2(dec, double), inc)
>>> inc_double_and_dec(10)
21

Do you see the pattern? First, we compose the first two functions, then we compose the newly created function with the next one and so on.

Let’s write this in Python.

import functools

def compose(*functions):
    def compose2(f, g):
        return lambda x: f(g(x))
    return functools.reduce(compose2, functions, lambda x: x)

Or in a more compact way:

def compose(*functions):
    return functools.reduce(lambda f, g: lambda x: f(g(x)), functions, lambda x: x)

注:親自測試過上述形式可以更加簡潔,如下:

def compose(*functions):
    return functools.reduce(lambda f, g: lambda x: f(g(x)), functions)

    ##lambda f, g: lambda x: f(g(x))這條語句的變量排列順序非常重要,它會影響函數的最後複合形式,
    ##例如functions=(f1,f2,f3,f4),那麼上述函數最終產生的函數形式爲:f1(f2(f3(f4(x))))
    ##如果lambda f, g: lambda x: f(g(x))這條語句變爲:
    ##lambda f, g: lambda x: g(f(x))這條語句,
    ##例如functions=(f1,f2,f3,f4),那麼上述函數最終產生的函數形式爲:f4(f3(f2(f1(x))))

下面我修改了f,g的順序,做的實驗如下:

def compose(*functions):
	return functools.reduce(lambda f, g: lambda x: g(f(x)), functions, lambda x: x)

inc_double_and_dec = compose(dec, double, inc)
a=inc_double_and_dec(10)
print (a)##輸出19

Example:

>>> inc_double_and_dec = compose(dec, double, inc)
>>> inc_double_and_dec(10)
21

Note that functools.reduce is also called fold.

Edit: Handle the case when the list of functions is empty. Thanks to Matthew Singer for catching this!

Multiple-argument functions

The reason why implementations get complex is because they support multiple-argument functions. But there is no need to do so, because any function can be transformed to a one-argument function using higher-order functions such as functools.partial, decorators or our own functions.

Examples:

>>> def sub(a, b):
...     return a - b
...
>>> pipeline = compose(functools.partial(sub, b=4), operator.neg)
>>> pipeline(-6)
2
>>> def second(*args):
...     return args[1]
...
>>> def second_wrapper(lst):
...     return second(*lst)
...
>>> pipeline = compose(second_wrapper, list, range)
>>> pipeline(5)
1

If you want to learn about functional programming in Python, I recommend reading https://docs.python.org/3/howto/functional.html.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章