2011年9月10日 星期六

lambda 運算式

在Python中,函式是一級(First-class)公民,也就是說,在Python中,函式是物件,為function的實例。如果你要定義一個函式,基本上是使用def來定義,如同 def 陳述句 中所說明過的,例如你要定義一個最大值的函式:

def max(m, n):
    return m if m > n else n

print(max(10, 3))  # 顯示 10

你可以用lambda運算式來定義函式,執行運算式時將會產生函式物件。 例如,上面的max函式,可以用以下的方式定義:
max = lambda m, n: m if m > n else nprint(max(10, 3))  # 顯示 10
      

lambda的語法是:
lambda arg1, arg2, ....: expression

lambda中arg1、arg2等就相當於定義函式時的參數,之後你可以在expression中使用這些參數。注意!lambda是運算式,不是陳述句,你在:之後的也必須是運算式,lambda中也不能有區塊,這表示一些小的運算任務你可以使用lambda,而較複雜的邏輯你可以使用def來定義。

基本上,lambda會產生function的實例,所以在
def 陳述句 中所提到的參數定義與引數指定方式,對於lambda所產生的function實例都是適用的。

在Python中缺少其它語言中的switch陳述句,以下結合字典物件與lambda模擬switch的示範:
score = int(input('請輸入分數:'))
level = score // 10
{
    10 : lambda: print('Perfect'),
    9  : lambda: print('A'),
    8  : lambda: print('B'),
    7  : lambda: print('C'),
    6  : lambda: print('D')
}.get(level, lambda: print('E'))()

在上例中,字典物件中的值的部份是lambda所建立的函式物件,你使用get()方法指定鍵,如果有符合的鍵,就傳回對應的函式物件並執行,否則就傳回get()第二個引數所指定的函式並執行,這模擬了switch中default的陳述。


在Python中,函式是function的實例,所以你可以自由傳遞,將一個函式中所定義的函式傳回也是常見的應用。例如:
def add(n1):
    def func(n2):
        return n1 + n2
    return func

print(add(1)(2))  # 顯示 3

從一個函式中呼叫另一個函式,這是函式程設中鞣制(Curry)的概念。所謂鞣製,是指將接受多個參數的函式,改為接受單一參數的函式,在函式執行過後傳回一個函式物件再套用剩下的參數,就像是將兩個函式鞣製在一起。

另一個值得注意的是,n1參數的存活期間,本應跟隨著add()函式呼叫完成就結束,不過因為n1被綁定在func()函式之中,形成了一個閉包(Closure)

所謂的閉包(Closure),是指一個函式物件(或 函式值)在建立時,綁定了當時作用範圍(Scope)下有效的自由變數(Free variable)。所 以支援閉包的語言,必須有支援一級函式(First-class function),建立函式物件並不等於建立閉包,建立函式物件時同時綁定了某個(些)自由變數,該函式物件才稱之為閉包。

在Python中,確實支援閉包的概念。例如:
>>> def outer():
...     x = 10
...     def inner():
...         print(x)
...     inner()
...     x = 20
...     return inner
...
>>> f = outer()
10
>>> f()
20
>>>


在上例中,inner()確實綁定了區域變數x,在outer()內部呼叫inner()時顯示的是10,而後改變了x為20,由於inner()綁定了x,所以傳回的函式執行時,顯示x的值為20。

不過實際上在應用時,還是得小心一點。例如:

>>> def func():
...     x = 10
...     def getX():
...         return x
...     def setX(n):
...         x = n
...     return (getX, setX)
...
>>> getX, setX = func()
>>> getX()
10
>>> setX(20)
>>> getX()
10
>>>


在上例中,func()中的setX()宣告的x,其實是setX()中的區域變數x,其覆蓋了外部func()的x,所以你的n是指定給區域變數x。

回到鞣製的討論,實際上其應用,在於先針對既有的資料先行作運算並傳回未運算的函式,待後續資料備妥後再完成整個所需的運算結果。一個例子像是
因 式分解,可以先準備好一定長度的質數表,之後利用該質數表來進行因式分解。例如:
import math

def prepare_factor(max):
    prime = [1] * max
    for i in range(2, int(math.sqrt(max))):
        if prime[i] == 1:
            for j in range(2 * i, max):
                if j % i == 0:
                    prime[j] = 0
    primes = [i for i in range(2, max) if prime[i] == 1] # 質數表
           
    def factor(num):
        list = []
        i = 0
        while primes[i] ** 2 <= num:
            if num % primes[i] == 0:
                list.append(primes[i])
                num //= primes[i]
            else:
                i += 1
        list.append(num)
        f = [0] * len(list)
        for i in range(len(f)):
            f[i] = list[i]
        return f
        
    return factor

factor = prepare_factor(1000)
print(factor(100))  # 顯示 [2, 2, 5, 5]
print(factor(500))  # 顯示 [2, 2, 5, 5, 5]
print(factor(800))  # 顯示 [2, 2, 2, 2, 2, 5, 5]

沒有留言:

張貼留言