Python二次元世界-Lisp的帝國斜陽 lambda與closure

Python二次元世界-函數式編程python

Function , lambdaclosure 程序員

 

本章講述Python語言自Lisp語言演變而來的一些高級函數編程技巧 如閉包(closure) 面試

匿名函數(lambda 生成器(yield) 嵌套做用域(nested scope)

好的
Python程序員必須熟練掌握這幾種特性 能快速地將代碼移植到類Lisp語言 scheme Emacs-Lisp JavaScript Java8

算法

特別提示∶在您瀏覽本教程時,不要強行記憶。記住一點∶在使用中學習。shell

1.         Function編程

Python的函數的本質是對象方法和模塊屬性
聲明/定義一個函數 def foo(): print 「bar」
函數做用於對象 object/reference foo
函數的調用 foo() or obj.foo()
若是沒有return語句 Python函數默認返回值是None

數組

>>> def hello():

... print 'hello world'

>>> 

>>> res = hello()

hello world

>>> res

>>> print res

None

>>> type(res)

<type 'None'>

不一樣其餘編程語言 Python不能夠擁有多個返回值 可是你能夠返回一個類型爲list的對象單元或是元組 等同於一次返回多個值 典型的有 os.walk() return (root,path,???)閉包

def foo(): return ['xyz', 1000000, -98.6] def bar(): return 'abc', [42, 'python'], "Guido"


爲保證返回值的不可變 可使用元組 os.walk()app

def foo(): return 'xyz', 1000000, -98.6 >>> rs = (x,y,z) = foo() >>> rs ('xyz', 1000000, -98.6) >>> x ‘xyz’ >>> x = 1 >>> x 1 >>> rs = 1 TypeError: 'tuple' object does not support item assignment #元組不能夠被修改 參數列表(): func(*lt, **kw) 帶有一個*號的參數是一個元組tuple,兩個星號爲字典dict

任何帶有關鍵字參數的函數 均可以接受不固定的參數列表 A=’B’的賦值被保存到**kw關鍵字典 使用kw[‘xxx’]進行引用 直接使用‘A’參數的賦值被保存到 *lt 關鍵字列表 引用方式同樣

dom

#########領略函數式編程的樂趣##########

第一課 example-1 makeFun.py

#!c:/python27/python.exe #coding: utf-8 __version__=0.1 __author__ ="vivianxiongping@gmail.com" import random as ri def guessguess(): '''猜一個100之內的隨機整數''' print '歡迎來到猜數字 輸入1-100之內的數字 [quit]退出' guess = 0 num = ri.randint(1,100) while guess != num: guess = input("請輸入一個數字: ") if guess > num: print 'too high' elif guess < num: print 'too low' else : print '''Binggo! the number is %d''' % num return 1 def gamble(): print '''*********歡迎來到賭大小********* 輸入[1]表明大 [0]表明小 [quit]退出\n''' yourchoice = '' done = False while not done: dice = lambda : ri.randint(1,6) x,y,z = dice(),dice(),dice() yourchoice = input('買入大小盤[1/0]?') if yourchoice in [0,1]: rs = 1 and (x+y+z)>=9 or 0 print '''Binggo! 骰子A[%d],骰子B[%d],骰子C[%d],買盤結果%d 注意讀做(tou)骰子 ''' % (x,y,z,x+y+z) tmp = "大" if yourchoice else "小" print '您買的是[',tmp,']' if yourchoice == rs: print '贏!' else : print '輸!' goahead = raw_input( '是否繼續? [y]') done = False if goahead == 'y' else True else: print "歡迎再來...\n退出遊戲" return 1 def bada_quit(direcs): for i in answers[3:]: if direcs in i : return True else : continue answers = [1,2,3,'quit','exit'] def main(): choice = '' while choice not in answers : choice = raw_input(""" 歡迎來到做死v1.0,你能夠選擇 1.鍛鍊智力 2.碰碰運氣 3.退出. [Q/quit] """) if choice == str(answers[0]): #Python沒有switch guessguess() elif choice == str(answers[1]): gamble() elif choice == str(answers[2]): print '退出...' break elif bada_quit(choice): print '退出...' break else : print "輸出12進入遊戲,輸入quit/Q退出..." continue if __name__=='__main__': main() 


歡迎來到做死v1.0,你能夠選擇
1.做死
2.繼續做死
3.退出. [Q/quit]
2
*********歡迎來到賭大小*********
輸入[1]表明大 [0]表明小 [quit]退出

買入大小盤[1/0]?0
Binggo! 骰子A[4],骰子B[4],骰子C[3],買盤結果11
注意讀做(tou)骰子
您買的是[ 小 ]
輸!
是否繼續? [y]y
買入大小盤[1/0]?1
Binggo! 骰子A[4],骰子B[6],骰子C[6],買盤結果16
注意讀做(tou)骰子
您買的是[ 大 ]
贏!
是否繼續? [y]quit
歡迎再來...
退出遊戲

歡迎來到做死v1.0,你能夠選擇
1.做死


根據二分查找策略 最多須要lg(n)+1次機會可得到答案數字 猜數字遊戲 在後面能夠進行升級 限制玩家的答題次數

 

寫完程序makeFun.py函數式編程的入門差很少結束了 開始下一個話題以前 請準備鐵觀音或者咖啡3

 

2.         Lambda

 

Python 保留了Haskell語言的匿名函數功能 使用lambda關鍵字定義匿名功能塊 區別於def關鍵字 lambda中的全部指針所有失效 python解釋器將再也不建立屬於該函數的stack區間保留臨時變量 它徹底就是一個表達式 相似於C語言中的宏 . 但能夠外部引用lambda指針 能夠接收參數 也能夠遞歸調用

>>> true   = lambda : True

>>> false = lambda : False

#改寫關鍵字TrueC Java程序員更習慣的 true

平方根函數

>>> sqrt = lambda x : x**0.5

>>> sqrt(3)

1.7320508075688772

次方函數

>>> pow = lambda x,y=2:x**y

>>> pow(3)

9

>>> pow(3,5)

243

lambda通常都是極客使用 爲了寫出積極複雜但精簡的代碼 lambda一般同一些回調函數一塊兒使用 主要有 apply reduce map filter

 

結合列表解析與回調函數 代碼將變得很複雜

Lambda-用例1.1 篩偶數

[x for x in range(100) if x%2]

[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41,

43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81,

83, 85, 87, 89, 91, 93, 95, 97, 99]

來看剛纔的例子

Lambda-用例1.2 切偶數

map(lambda x:x if x%2 else None,[x for x in range(100)])[1::2]


[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41,

43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81,

83, 85, 87, 89, 91, 93, 95, 97, 99]

map, filter, apply, reduce 相似PHP中的array walk() 接收一個函數指針用於數組的回調 返回自己或者None(直接做用於數組) apply filter 已經在2.4版本中被廢棄 建議使用map reduce完成一樣的功能 python三元操做符 x = sth if a else b 或者 x = sth and a or b


一些更有趣的例子 

Lambda 用例2 階乘

 

#!/usr/bin/env python #-*- encoding: utf-8 -*-

__author__='vikid'

__version__=1.0

 
#C新人

def factorial_v1(n=5):

    if n == 0:

        return 1

    else:

        return factorial_v1(n-1)*n


#菜鳥

def factorial_v2(n=5,rs=1):

    for i in xrange(2,n+1):

        rs *= i

    return rs


#大蝦

def factorial_v3(n=5):

    return n and factorial_v2(n-1)*n or 1

 

#極客

def factorial_v4(n=5):

    f = lambda n: reduce(lambda a,b:a*b ,xrange(2,n+1))

    return f(n)

   

#測試

from random import choice

funcs,op = {

    "1":factorial_v1,

    "2":factorial_v2,

    "3":factorial_v3,

    "4":factorial_v4

},"1234"

 

for i in range(2,51):

    which       =       choice(op)

    rs          =       str(funcs[which](i))

    showrs      =       rs.rjust(60) if i <=25 else rs.ljust(30)

    print """v%s[%s] = %s """ % (which,str(i).center(4),showrs) 

 

v2[ 2 ] = 2 v3[ 3 ] = 6 v2[ 4 ] = 24 v4[ 5 ] = 120 v4[ 6 ] = 720 v4[ 7 ] = 5040 v4[ 8 ] = 40320 v2[ 9 ] = 362880 v2[ 10 ] = 3628800 v3[ 11 ] = 39916800 v2[ 12 ] = 479001600 v2[ 13 ] = 6227020800 v4[ 14 ] = 87178291200 v4[ 15 ] = 1307674368000 v3[ 16 ] = 20922789888000 v4[ 17 ] = 355687428096000 v3[ 18 ] = 6402373705728000 v1[ 19 ] = 121645100408832000 v3[ 20 ] = 2432902008176640000 v4[ 21 ] = 51090942171709440000 v2[ 22 ] = 1124000727777607680000 v4[ 23 ] = 25852016738884976640000 v2[ 24 ] = 620448401733239439360000 v3[ 25 ] = 15511210043330985984000000 v2[ 26 ] = 403291461126605635584000000 v1[ 27 ] = 10888869450418352160768000000 v1[ 28 ] = 304888344611713860501504000000 v1[ 29 ] = 8841761993739701954543616000000 v2[ 30 ] = 265252859812191058636308480000000 v2[ 31 ] = 8222838654177922817725562880000000 v2[ 32 ] = 263130836933693530167218012160000000 v4[ 33 ] = 8683317618811886495518194401280000000 v1[ 34 ] = 295232799039604140847618609643520000000 v2[ 35 ] = 10333147966386144929666651337523200000000 v2[ 36 ] = 371993326789901217467999448150835200000000 v3[ 37 ] = 13763753091226345046315979581580902400000000 v4[ 38 ] = 523022617466601111760007224100074291200000000 v1[ 39 ] = 20397882081197443358640281739902897356800000000 v4[ 40 ] = 815915283247897734345611269596115894272000000000 v1[ 41 ] = 33452526613163807108170062053440751665152000000000 v2[ 42 ] = 1405006117752879898543142606244511569936384000000000 v3[ 43 ] = 60415263063373835637355132068513997507264512000000000 v2[ 44 ] = 2658271574788448768043625811014615890319638528000000000 v4[ 45 ] = 119622220865480194561963161495657715064383733760000000000 v2[ 46 ] = 5502622159812088949850305428800254892961651752960000000000 v3[ 47 ] = 258623241511168180642964355153611979969197632389120000000000 v3[ 48 ] = 12413915592536072670862289047373375038521486354677760000000000 v1[ 49 ] = 608281864034267560872252163321295376887552831379210240000000000 v2[ 50 ] = 30414093201713378043612608166064768844377641568960512000000000000


lambda代碼絕大多數人看不懂 甚至有時本身也看不懂 但這正是lambda的意義 人人都有極客的潛質 當你仍是一個菜鳥的時候根本沒法寫出火星人代碼 可是當你習慣了這樣的書寫方式以後 你會發現全世界90%的代碼都醜陋至極 尤爲做爲一個Python程序員 牢記 有且只有一種最簡單的辦法 雖然有時候你得爲這種簡單犧牲一些性能做爲代價

 

>>> import this

The Zen of Python, by Tim Peters

 

There should be one-- and preferably only one --obvious way to do it.

Beautiful is better than ugly –long live the Lambda!

>>>

 

reduce函數是python2.4以後的一個里程碑函數 它取代了醜陋和沒必要要的apply filter 甚至是map 它的工做方式是這樣的:

reduce(1..50)->re((((((12)3)4)5)6)..49)

一次接受2個參數進行運算 用一個佔位符保存中間結果 直到表達式迭代完畢 返回佔位符中的保存值 


值得一提的是 根據個人經驗 判斷一個python程序員的水平最直觀的方式就是看他/她能不能使用回調函數與嵌套做用域來書寫lambda代碼:

Lambda-用例3.1 斐波那契數列

return map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1: f(x,f),range(100))

[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]

 

必須指出的是 Python3000以後 再也不鼓勵使用回調函數 事實上 即便不使用reduce map filter 同樣能寫出精簡的lambda表達式

解決辦法就是: 列表解析 […]

Lambda-用例3.1素數

primes = lambda n:[x for x in range(1,n) if not [y for y in range(2,int(x**0.5 +1)) if x % y == 0]] print primes(100)

[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]


列表解析 if 能夠不添加: 切一般位於表達式後半部

讓咱們來爲lambda添加註釋

Lambda-用例3.2素數

# [y for y ..]以後的列表解析 用於計算當前數n 的全部分解因子 例如 9的分解

# 因子爲[3] 3*3=9,10的分解因子爲[2,5] 2*5=105*2=10

# 有了新的素數算法 -- 沒有分解因子的數n,即只能被1和他自身分解的數 ->素數

# Pseudo code

# [x for x in range(n) #is Prime~# if [#分解因子切片#]==null ]

Primes = lambda n:[x for x in range(1,n) if not [y for y in \
range (2,int(x**0.5 +1)) if x % y == 0]]

固然了 樓上的代碼其實還能夠寫的更復雜 但我以爲已經沒有現實意義了 純屬火星人

primes = lambda n:[x for x in range(1,n) if not \
[y for y in range(2,map(lambda z: int(z**0.5)+1,[x])[0]) if x % y == 0]]

print primes(20)


3.         *Lambda尾聲 PFA 截斷函數

 

Python2.5之後反而引入了一個至關有趣 晦澀的特性 functool 模塊

該模塊下面有一個方法特別有意思 引入了PFA的概念 我不知道如何翻譯 姑且叫作截斷函數

打開python doc->Library Refercens->9.8 functools.partial

官方有一個很實用的列子  

>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18


咱們拓展一下例子 製做一個函數 用來計算16進制數的整數值

只須要改一行代碼

>>> from functools import partial
>>> hex2human = partial(int, base=16)
>>> basetwo('0x168')
360



Partial函數接收一個函數指針 2個關鍵字參數 而後傳遞這個函數的調用 這個特性被普遍使用與Python OOP特性 操做符重載 和方法重載 

<Python核心編程>一書中有一個關於partial方法很是經典的用例

Lambda-用例4.1使用截斷函數進行方法重載

#!/usr/bin/env python
#-*- encoding: utf-8 -*-

from functools import partial
import Tkinter
root = Tkinter.Tk()
MyButton = partial(Tkinter.Button, root,fg='white', bg='blue')
# 關鍵字參數被自動複製到MyButton構造方法中 每個實例不須要複製編碼參數
b1 = MyButton(text='更加優雅的寫法')
b2 = MyButton(text='兩個按鈕具備相同的構造參數')
qb = MyButton(text='退出', bg='red',command=root.quit)
b1.pack()
b2.pack()
qb.pack(fill=Tkinter.X, expand=True)
root.title('PFAs!')
root.mainloop()



4.         Closure

 

自版本2.1以後 Python全面支持Lisp的閉包特性 全局做用於 須要在單獨的行內聲明 任什麼時候候不要使用2.1以前的python解釋器 可能會產生兼容問題

x = 「nesting world」 def foo():      y = 「\b\n」 Global x x = 「hello world」 print x+y foo() #Hello world

 

習慣寫 schemeHaskell程序的人 每每會對python中的閉包(closure)特性感到十分親切 傳統的函數式編程沒有面向對象的諸多功能  好比類與封裝 繼承與多重繼承  可是有了closure 即便不使用class 關鍵字 也能夠模擬面向對象的功能

這十分狂妄地彰顯了Lisp語言的極客精神

更現實的意義在於 GUI程序設計和DB事務設計中 閉包被普遍使用 來限制變量的越權讀寫 這得益於closure對做用域的限制

 

Closure -用例1.1使用closure模擬面向對象

#/usr/bin/env python

#-*- coding: utf-8 -*-

import sys

__author__='vikid'

__version__=1.0

__doc__= '''Simple tool for making class-like objects using nested scopes and closures in Python 2.x'''

 

 

class Instance:

   pass

def classify(local_dict=None):

    o = Instance()

    if local_dict is None:

        local_dict = sys._getframe(1).f_locals

    vars(o).update(local_dict)

    return o

 

#########################

###  Simple example  ####

#########################

def Animal(name):

    'Animal-like class'

    def speak():

        print('I am', name)

    def set_name(newname):

        global name

        name = newname

    def __getitem__(key):

        return '%s is %sing' % (name, key.title())

    return classify()

d = Animal('哈士奇')

print(d.name)

d.speak()

d.set_name('小馬哥')

d.speak()

print(d['fetch'])

 

Closure -用例1.2 [2.py]使用closure模擬面向對象

def person(name="peter"):

    name = name

    def say_hello():

        return "my name is " + name

    return say_hello()


print person()

print person("Lita")

>>>2.py

My name is peter

My name is Lita

 

關於做用域最簡單的一句話總結就是 閉包函數能夠自由操做上層函數的變量 最高到達全局global lambda閉包同函數 反過來 上層函數不能修改閉包函數值 由於閉包是在上層函數建立以後建立的 當遇到內外衝突的變量 closure將忽略上層變量保留內部變量 這一個特性很是重要 不少Python JavaScript面試題考究這點

 

Closure -用例1.3 closure結合包裝器實現函數測試(from core python) 思考 函數1爲什麼明顯快於23

#filename: log4py.py 

#!c:/python272/python.exe
#-*- encoding: utf-8 -*-

'''
        usage:

         \

         |--   from log4py import logtest

         |--   @logtest()

         |--   def foo(): #do sthing

         |--   foo() #auto make it

'''

from time import time

import sys

 

#-------------------------閉包函數------------------------------

def logtest():

    "測試函數的執行用時 打印參數表"

    def show_args(f,*args,**kargs):

        print '''

funcs name  : %s

params      : %r

key params  : %r

..          : %s''' % (f.__name__,args,kargs,'###'*10)

 

    def logfuncs(f):

        def closure_log(*args,**kargs):

            now = time()

            try:

                return f(*args,**kargs)

            finally :

                show_args(f,*args,**kargs)

                print "Call timedelta: %s" % (time() - now)

        return closure_log

    try:   

        return logfuncs

#return {」foo」:foo,」bar」:bar}[para_father]

    except KeyError ,e :

        raise ValueError(e) ,'None'

 

----------------------------------------------------------------------------

#filename: log4py.py

 

#!/usr/bin/env python

#-*- encoding: utf-8 -*-

 

"""

輸入30之內的斐波那契數字的不一樣算法以及測試代碼

1,$s/22/30:w

"""

from log4py import logtest as log

 

@log()

def fibonacci_lambda(n):

    return map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1: f(x,f),range(n))

 

@log()

def fibonacci_recursion(n):

    def closures_fibonacci_recursion(p):

        """closure function 2 calc current fibo num"""

        return int(1 and p in [0,1] or closures_fibonacci_recursion(p-1) + closures_fibonacci_recursion(p-2))

    d = []

    for i in xrange(n):

        d.append(closures_fibonacci_recursion(i))

    return d

 

@log()

def fibonacci_pythonic(n):

    '''

Lift is short and i pythonic!

'''

    a,b,list = 0,1,[]

    for i in xrange(n):

        #get current fibonacci number and push it into list[]

        list.append(b)

        a , b = b , a + b

    return list

 

print fibonacci_pythonic(30)

print fibonacci_lambda(30)

print fibonacci_recursion(30)


funcs name  : fibonacci_pythonic
params      : (30,)
key params  : {}
..          : ##############################
Call timedelta: 0.00799989700317
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181
, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 83204
0]

funcs name  : fibonacci_lambda
params      : (30,)
key params  : {}
..          : ##############################
Call timedelta: 2.08600020409
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181
, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 83204
0]

funcs name  : fibonacci_recursion
params      : (30,)
key params  : {}
..          : ##############################
Call timedelta: 5.23500013351
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181
, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 83204
0]


包裝器是2.5之後的屬性 用於處理靜態類編寫工做 logtest() 自己不傳遞被包裝的函數指針fibonacci_xxx 而是他的閉包函數的第一個參數f接收函數指針 注意是一個指針而不是字符串(不一樣於PHP) 閉包內調用return f()來執行函數 #return {」foo」:foo,」bar」:bar}[para_father]你可使用這樣的方式來指定返回哪個閉包函數 儘管他們只有細微的差異 例如是否打印執行時間

 

但願下個月黃金能跌破1500 ~

相關文章
相關標籤/搜索