python核心編程筆記(10)- 函數和函數式編程

chap 10 函數和函數式編程


1.python中什麼都不返回的函數默認返回的對象類型是None;由於python當中的函數可以動態地確定類型而且函數能返回不同類型的值,所以函數的類型和返回值的類型沒有進行直接的關聯。在模擬c語言的函數重載時,要使用type()內建函數來做代理。


2.python中不允許在一個函數聲明之前去調用這個函數。


3.函數體內部創建函數對象的方式:a.內部/內嵌函數  b.lambda函數
    a.內部函數定義包含了外部函數裏定義的對象的引用(這個對象甚至可以是在外部函數之外),內部函數會變成被稱爲閉包


4.裝飾器:一般說來,當你包裝一個函數的時候,你最終會調用它。最棒的是我們能在包裝的環境下在合適的時機調用它。我們在執行函數之前,可以運行些預備代碼,如 post-morrem 分析,也可以在執行代碼之後做些清理工作。所以當你看見一個裝飾器函數的時候,很可能在裏面找到這樣一些代碼,它定義了某個函數並在定義內的某處嵌入了對目標函數的調用或者至少一些引用。從本質上看,這些特徵引入了 java 開發者稱呼之爲 AOP(Aspect Oriented Programming,面向方面編程)的概念。你可以考慮在裝飾器中置入通用功能的代碼來降低程序複雜度 。例如,可以用裝飾器來:引入日誌、增加計時邏輯來檢測性能、給函數加入事務的能力
    adas裝飾器以@開頭,接着是裝飾器函數的名字和可選的參數。緊跟着裝飾器聲明的是被修飾的函數,和裝飾函數的可選參數。
    eg.
        @decorator(dec_opt_args)
        def func2Bdecorated(func_opt_args):
    裝飾器可以堆疊起來:
    eg.
        @deco2
        @deco1
        def func(arg1, arg2, ...): pass
        等價於:
        def func(arg1, arg2, ...): pass
        func = deco2(deco1(func))
     帶參數的裝飾器,需要自己返回以函數作爲參數的裝飾器。換句話說,decomaker()用 deco_args 做了些事並返回函數對象,而該函數對象正是以 foo 作爲其參數的裝飾器。:
     eg.
        @decomaker(deco_args)
        def foo(): pass
        等價於:
        foo = decomaker(deco_args)(foo)


5.傳遞函數:
    因爲所有的對象都是通過引用來傳遞的,函數也不例外.當對一個變量賦值時,實際是將相同對象的引用賦值給這個變量。如果對象是函數的話,這個對象所有的別名都是可調用的。foo()是對函數對象的調用,foo是函數對象的引用
    我們甚至可以把函數作爲參數傳入其他函數來進行調用。
    eg.
    >>> def bar(argfunc):
    ...     argfunc()
    ... 
    >>> def foo():
    ...     print 'in foo'
    ... 
    >>> 
    >>> bar(foo)
    in foo
    
6. 形式參數(位置參數、默認參數)
   可變長度的參數:非關鍵字可變長參數(元組,函數調用中使用*來指定參數元組,所有的形式參數必須先於非正式的參數之前出現)、關鍵字變量參數(字典,函數調用中使用**來指定參數集合)
   異常:只要在函數調用時給出不正確的函數參數數目,就會產生一個 TypeError異常
   eg.
    
def newfoo(arg1, arg2, *nkw, **kw):
        'display regular args and all variable args'
        print 'arg1 is:', arg1
        print 'arg2 is:', arg2
        for eachNKW in nkw:
            print 'additional non-keyword arg:', eachNKW
        for eachKW in kw.keys():
            print "additional keyword arg '%s': %s" % (eachKW, kw[eachKW])


    方式a:
    >>> newfoo('wolf', 3, 'projects', freud=90, gamble=96)
    arg1 is: wolf
    arg2 is: 3
    additional non-keyword arg: projects
    additional keyword arg 'freud': 90
    additional keyword arg 'gamble': 96
    方式b:
    >>> newfoo(10, 20, 30, 40, foo=50, bar=60)
    arg1 is: 10
    arg2 is: 20
    additional non-keyword arg: 30
    additional non-keyword arg: 40
    additional keyword arg 'foo': 50
    additional keyword arg 'bar': 60
    方式c:
    >>> newfoo(2, 4, *(6, 8), **{'foo': 10, 'bar': 12})
    arg1 is: 2
    arg2 is: 4
    additional non-keyword arg: 6
    additional non-keyword arg: 8
    additional keyword arg 'foo': 10
    additional keyword arg 'bar': 12
    方式d:
    >>> aTuple = (6, 7, 8)
    >>> aDict = {'z': 9}
    >>> newfoo(1, 2, 3, x=4, y=5, *aTuple, **aDict)
    arg1 is: 1
    arg2 is: 2
    additional non-keyword arg: 3
    additional non-keyword arg: 6
    additional non-keyword arg: 7
    additional non-keyword arg: 8
    additional keyword arg 'z': 9
    additional keyword arg 'x': 4
    additional keyword arg 'y': 5


7.函數式編程
    匿名函數和lambda:一個完整的 lambda“語句”代表了一個表達式,這個表達式的定義體必須和聲明放在同一行。比如:
        lambda [arg1[, arg2, ... argN]]: expression
    內建函數:
        apply(func[,nkw][,kw]):用可選的參數來調用 func,nkw 爲非關鍵字參數,kw關鍵字參數;返回值是函數調用的返回值
        filter(func, seq):調用一個布爾函數func來迭代遍歷每個seq中的元素;返回一個使func返回值爲ture的元素的序列。
        map(func, seq1[,seq2...]):將函數 func 作用於給定序列(s)的每個元素,並用一個列表來提供返回值;如果 func 爲 None, func 表現爲一個身份函數,返回一個含有每個序列中元素集合的 n 個元組的列表。
        reduce(func, seq[, init]):將二元函數作用於 seq 序列的元素,每次攜帶一對(先前的結果以及下一個序列元素),連續的將現有的結果和下一個給值作用在獲得的隨後的結果上,最後減少我們的序列爲一個單一的返回值;如果初始值 init 給定,第一個比較會是 init 和第一個序列元素而不是序列的頭兩個元素。


8.偏函數應用
    這裏主要介紹了functools模塊下的partial函數的使用:
    eg:
    對乘法運算函數add和mul的偏應用 
    >>>from operator import add, mul
    >>>from functools import partial
    >>>add1 = partial(add, 1)    # add1(x) 等同於 add(1, x)
    >>>mul100 = partial(mul, 100) # mul100(x) 等同於 mul(100, x)
    >>>add1(3)
    4
    >>>mul100(3)
    300
    對字符串轉換爲整型函數int(str,base)的偏應用
    >>>baseTwo = partial(int, base = 2)
    >>>baseTwo('10010')
    18
    
9.變量作用域
    當搜索一個標識符的時候,python 先從局部作用域開始搜索。如果在局部作用域內沒有找到那個名字,那麼就一定會在全局域找到這個變量否則就會被拋出 NameError 異常。如果將全局變量的名字聲明在一個函數體內的時候,全局變量的名字能被局部變量給覆蓋掉。
    爲了明確地引用一個已命名的全局變量,必須使用 global 語句。
    閉包:如果在一個內部函數裏,對在外部作用域(但不是在全局作用域)的變量進行引用,那麼內部函數就被認爲是 closure。定義在外部函數內的但由內部函數引用或者使用的變量被稱爲自由變量。閉包的詞法變量不屬於全
局名字空間域或者局部的--而屬於其他的名字空間,帶着“流浪"的作用域。注意這不同於對象因爲那些變量是存活在一個對象的名字空間但是閉包變量存活在一個函數的名字空間和作用域。
    eg.簡單的閉包栗子:
        def counter(start_at=0):
            count = [start_at]
            def incr():
                count[0] += 1
                return count[0]
            return incr
        >>>count = counter(5)
        >>>print count()
        6
        >>>print count()
        7
        >>>print count()
        8
    eg:追蹤閉包詞法的變量
    #!/usr/bin/python
    output = '<int %r id=%#0x val=%d>'
    w = x = y = z = 1

    def f1():    
        x = y = z = 2
    
        def f2():       #w全局變量,y,z是自由變量,x是閉包詞法變量,可以被f2.func_closure追蹤到
            y = z = 3
    
            def f3():       #w全局變量,z是自由變量,x,y是閉包詞法變量,可以被f3.func_closure追蹤到
                z = 4
                print output % ('w',id(w),w)
                print output % ('x',id(x),x)
                print output % ('y',id(y),y)
                print output % ('z',id(z),z)
    
            clo = f3.func_closure
            if clo:
                print "f3 closure vars:", [str(c) for c in clo]
            else:
                print "no f3 closure vars"
            f3()
    
        clo = f2.func_closure
        if clo:
            print "f2 closure vars:", [str(c) for c in clo]
        else:
            print "no f2 closure vars"
        f2()
    
    clo = f1.func_closure
    if clo:
        print "f1 closure vars:", [str(c) for c in clo]
    else:
        print "no f1 closure vars"
    f1()

    eg:裝飾器和閉包的應用

    #!/usr/bin/python

    from time import time

    def logged(when):      #when的參數可以是pre或者post
        def log(f, *args, **kargs):     #hello傳進來替代f
            print '''Called:
    function: %s
    args: %r
    kargs: %r''' % (f, args, kargs)
    
        def pre_logged(f):
            def wrapper(*args, **kargs):
                log(f, *args, **kargs)    #在return f(*args, **kargs)之前執行log(f, *args, **kargs)
                return f(*args, **kargs)
            return wrapper
    
        def post_logged(f):
            def wrapper(*args, **kargs):
                now = time()
                try:
                    return f(*args, **kargs)
                finally:
                    log(f, *args, **kargs) #在return f(*args, **kargs)之後執行log(f, *args, **kargs)
                    print "time delta: %s" % (time()-now)
            return wrapper
    
        try:
            return {"pre": pre_logged,
                "post": post_logged}[when]
        except KeyError, e:
            raise ValueError(e), 'must be "pre" or "post"'
                
    @logged("pre")
    @logged("post")
    def hello(name):       #hello = logged("pre")(logged("post")(hello))
        print "Hello,", name
        
    hello("World!")   #此處實際運行logged("pre")(logged("post")(hello))
    輸出結果是:
    Called:
    function: <function hello at 0x7ff98ac667d0>
    args: ('World!',)
    kargs: {}
    Hello, World!
    Called:
    function: <function wrapper at 0x7ff98ac66848>
    args: ('World!',)
    kargs: {}

    time delta: 0.000123023986816


10.遞歸:函數對其自身進行調用


11.生成器

    協同程序的概念:協同程序是可以運行的獨立函數調用,可以暫停或者掛起,並從程序離開的地方繼續或者重新開始。當協同程序暫停的時候,我們能從其中獲得一箇中間的返回值,當調用回到程序中時,能夠傳入額外或者改變了的參數,但仍能夠從我們上次離開的地方繼續,並且所有狀態完整。掛起返回出中間值並多次繼續的協同程序被稱爲生成器,那就是 python 的生成器真正在做的事。
    python生成器,從句法上講,生成器是一個帶 yield 語句的函數。一個函數或者子程序只返回一次,但一個生成器能暫停執行並返回一箇中間的結果----那就是 yield 語句的功能, 返回一個值給調用者並暫停執行。當生成器的 next()方法被調用的時候,它會準確地從離開地方繼續。
    eg.
def counter(start_at=0):
        count = start_at
        while True:
            val = (yield count)   #   記號*  
            print val
            if val is not None:   #   分支**1
                print '***'
                count = val
            else:                 #   分支**2
                count += 1
    
    運行結果:
    >>> cou = counter()
    >>> cou.next()
    0
    >>> cou.next()
    None
    1
    >>> cou.next()
    None
    2
    >>> cou.next()
    None
    3
    >>> cou.send(7)
    7
    *******
    7
    >>> cou.next()
    None
    8
    >>> cou.next()
    None
    9
    >>> cou.close()
    >>> cou.next()
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    StopIteration

有幾點需要注意的: 
    a.養成對yield count加小括號的習慣,來區別(yield count) + 1和yeild count + 1    
    b.當程序第一次運行到*的時候,這時候count被返回給調用者,但是(yield count)沒有返回(也可以理解爲沒有執行返回結果),當下次使用next()的時候,(yield count)返回一個None給val,程序繼續向下執行進入分支**2,直到再次運行到記號*的位置。
    c.當使用send(val),給生成器傳值的時候,其實相當於直接給記號*標記行中等號左邊的val傳值,此時程序會進入分支**1,對count重新定義新的值
    d.close()用來終結生成器




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