Python學習筆記整理(十二)

一、函數基礎 函數可以計算出一個返回值。作用:最大化程式碼重用,最小化程式碼冗餘,流程的分解 1、函數相關的語句和表達式 語句        例子 Calls        myfunc(『diege','eggs',meat=lit) #使用函數 def,return,yield      def adder(a,b=1,*c):                           return a+b+c[0] global        changer():                 global x;x='new' lambda        Funcs=[lambad x:x**2,lambad x:x*3] 2、編寫函數 def是可執行的程式碼,實時執行的,Python中所有語句都是實時執行的,if,while,def可嵌套,可以出現在任何地方,但往往包含在模組文件中, 並早模組導入時運行,函數還可以通過嵌套到if語句中去實現不同的函數定義。 def創建了一個對象並將其賦值給某一個變數名。 return將一個結果對象發送給調用者。 函數是通過賦值(對象引用)傳遞的。

參數通過賦值傳遞給函數。 global聲明了一個模組級的變數並被賦值。 參數,返回值以及變數並不是聲明 def語句 def語句將創建一個函數對象並將其賦值給一個變數名。一般格式如下: def <name>(arg1,age2,…,agrN):     <statements>      return <value> 函數主體往往都包含了一個return語句(不是必須的),如果它沒有出現,那麼函數將會在控制流執行完函數主體時結束,技術上,沒有返回值的函數自動返回了none對象。 return可以在函數主體中的任何地方出現。它表示函數調用的結束,並將結果返回至函數調用處。 函數通過嵌套到if語句中去實現不同的函數定義的格式: if test:     def func():         … else:     def func() func()    #call 3、例子1 定義 >>> def Dtest(x,y): …     return x*y 使用(call) >>> Dtest(3,5) 15 >>> Dtest(3.14,3) 9.42 >>> Dtest('A',4)     'AAAA' 這裡又看到Python中的多態了 Dtest函數中的表達式x*y的意義完全取決於x和y的對象類型。 類似的多態操作還包括:print,index,*操作符 >>> Dtest('A','A') #會報錯,但不要嘗試判斷傳入參數的對象類型,這樣實質上破壞了函數的靈活性,把函數限制在特定的類型上。任何支援函數所預期的結果的對象都能用。(結果一詞是指函數所執行的一組方法和表達式運算符) 4、例子2 尋找序列的交集 定義 >>> def Intersect(seq1,seq2): …     res=[] …     for x in seq1: …             if x in seq2: …                     res.append(x) …     return res 調用 >>> Intersect([1,3,5,6,7,9],[2,4,5,7,8,10]) [5, 7] >>> s1='SPAM' >>> s2='sCaM' >>> Intersect(s1,s2)                         ['M'] 重訪多態,這些編寫這個函數是多態,它可以支援多種類型 >>> Intersect([1,3,5,6,7,9],(1,4))              [1] 技術上講,文件也可以做為該函數的第1個參數,但不能作為第2個參數。在一個文件中搜索2個參數 >>> Intersect(open('/etc/rc.conf').read(),'apache') 5、本地變數 例子2中.res變數在python中稱為本地變數,這個變數只是在def內的函數中是可見的,並且僅在函數運行時是存在的。所有函數內部進行賦值的變數名 都默認為本地變數,。 *req是明顯被賦值過的,所以它是一個本地變數 *參數也是通過賦值被傳入的,所以seq1和seq2也是本地變數。 *for循環將元素賦值給了一個變數,所以變數x也是本地 二、作用域和參數 (一)作用域 python作用域:變數定義以及查找的地方 參數傳遞:傳遞給函數作為其輸入對象的方式 1、作用域法則 Python創建,改變或者查找變數名都是在所謂的命名空間(一個保存變數名的地方)中進行。作用域這個術語指的就是命名空間。 也就說,在程式碼中變數名被賦值的位置決定了這個變數名能被訪問到的範圍 一個函數所有變數名都與函數的命名空間相關聯。 *def內定義變數名def內使用 *def之中的變數名與def之外的變數名不發生衝突,使用別處相同的變數名也沒問題。 x=99 def func()     x=88 函數定義了本地作用域,而模組定義了全局作用域。兩作用域關係。 *內嵌的模組是全局作用域:對於外部的全局變數就成為了一個模組對象的屬性。 *全局作用域的作用範圍僅限單個文件:不要被全局迷惑,這裡的全局是一個文件的頂層的變數名,僅對這個文件內部的程式碼而言是全局。 Python中,沒有一個無所不包的情景文件作用域。替代的方法是,變數名由模組文件隔開,必須精準地導入一個模組文件才能偶使用這文件中 定義的變數名, *每次對函數的調用都創建了一個新的本地作用域。 *賦值的變數名廢除聲明為全局變數,否則均為本地變數。 *所用的變數名都可以歸納為本地,全局,或者內置。(內置:ptyhon預定義的__builtin__模組提供的) 2、變數名解析:LEGB原則 對一個def語句 *變數名引用分為三個作用域進行查找:首先查找本地,然後是函數內(如果有),之後全局,最後內置。 *默認情況下,變數名賦值會創建或改變本地變數 *全局聲明將賦值變數名映射到模組文件內部的作用域。 3、作用域實例 #定義全局作用域 >>> X=99 >>> def func(Y): …     Z=X+Y   #本地作用域 …     return Z … >>> func(1)     100 全局變數:X,func 本地變數:Y,Z 4、內置作用域 需要導入__builtin__ >>> import  __builtin__ >>> dir(__builtin__) 裡面前一半是內置異常,後一半是內置函數 5、global global語句包含關鍵字global *全局變數是位於模組文件內部頂層的變數名 *全局變數如果是在函數內部被賦值的話,並需經過聲明 *全局變數名在函數的內部不經過聲明也可以被引用 >>> X=88      >>> def func(): …     global X …     X=99 … >>> func() >>> print X 99 >>> y,z=1,2 >>> def Ftest(): …     global x …     x=y+z … >>> Ftest() >>> x 3 6、最小化全局變數 (二)傳遞參數 *參數的傳遞是通過自動將對象賦值給本地變數來實現的。 *在函數內部的參數名的賦值不會影響調用者。 *改變函數的可變對象參數的值也許會對調用者有影響。 換句話說,因為參數是簡單的通過賦值進行對象的傳遞,函數能夠改變傳入的可變對象,因此其結果會影響調用者。 *不可變參數是「通過值」進行傳遞。 像整數和字元串這樣的對象是通過對象引用而不是拷貝進行傳遞的,但是因為你無論如何都不可能在原處改變不可變對象,實際的效果就是很像創建了一份拷貝。 可變對象是通過「指針」進行傳遞的。 1、參數和共享引用 >>> def changer(a,b): …     a=2 …     b[0]='diege' … >>> X=1 >>> L=[1,2] >>> changer(X,L) >>> X,L (1, ['diege', 2]) 函數內部對參數或者參數的分片賦值了,X沒改變,L改變了,X,Y都是全局變數。這說明了數字字元串不可變參數函數無法改變。 而列表,字典等可改變參數可以在執行函數調用後改變。 這裡a是函數的本地變數名,第一個賦值對函數調用者沒有影響。它僅簡單的修改了本地變數名a,並沒有改變參數a綁定的函數調用者的變數名X。 b也是一個本地變數名,但是他被傳遞給了一個可變對象。因為第二個賦值是一個在原處發生的對象改變(如果是b='diege'就沒有改變,因為這樣只改變本地變數名),對函數中b[0]進行賦值的結果是在函數返回後影響L的值。事實上我們沒有修改b,修改是是b當前所引用 對象的一部分,並且這個改變將影響調用者。 2、避免可變參數的修改 在Python中,默認通過引用(也就是指針)進行函數的參數傳遞。如果不想在函數內部在原處的修改影響傳遞給它的對象。那麼,能夠簡單的創建一個可變對象的拷貝。我們總是能夠在調用時對列表進行拷貝L=[1,2] changer(X,L[:]) 如果不想改變傳入的對象,無論函數是如何調用的,我們可以在函數內部進行拷貝,避免可變參數的修改 >>> def changer(a,b): …     a=2 …    b=b[:] …     b[0]='diege' … 3、對參數輸出進行模擬 return能夠返回任意種類的對象,它也能返回多個值,如這些值被封裝進一個元組或者其他的集合類型 >>> def mul(x,y): …     x=2 …     y=[3,4] …     return x,y … >>> X=1 >>> L=[1,2] >>> mul(X,L) (2, [3, 4]) >>> X,L=mul(X,L)  >>> X,L (2, [3, 4]) 4、特定參數匹配模型 參數在ptyhon中總是通過賦值進行傳遞,傳入的對象賦值給了在def頭部的變數名。儘管這樣,在模型的上層,python提供了額外的工具,該工具改變了調用過中,賦值時參數對象匹配在頭部的參數名的優先順序。這些工具是可選的。 默認情況下,參數是通過其位置進行匹配的,從左到右,而且必須精確地傳遞和函數頭部參數名一樣多的參數。還能夠定義變數名進行匹配,默認參數值(arg2=10),以及對於額外參數的容器,必須要根據變數名匹配對象,匹配完成後在傳遞機制的底層依然是賦值。這些工具對於編寫庫文件的人來說,要比應用程式開放者更有用。 規則 *位置:從左到右進行匹配 *關鍵字參數:通過參數名進行匹配。 【調用者】可以定義那個函數接受這個值,通過在調用時使用參數的變數名,使用name=value這種語法。 *默認參數:為沒有傳入值得參數定義參數值【定義函數時】 如果調用時傳入的值過於少的話,函數能夠為參數定義接受的默認值,在函數定義中使用name=value *可變參數:收集任意多基於位置或關鍵字的參數 以*開頭,收集任意多的額外參數 *可變參數:傳遞任意多的基於位置或關鍵字的參數。【調用時】 調用者能夠再使用*語法去將參數集合打散,分成參數。這個*與函數頭部的*恰恰相反。在函數頭部他意味著收集任意多的參數,而在調用者中意味著傳遞任意多的參數。 總結與特定模式有關的語法: 語法            位置    解釋 func(value)        調用者    常規參數,通過位置進行匹配 func(name=value)    調用者    關鍵字參數,通過變數名匹配 func(*name)        調用者    以name傳遞所有的對象,並作為獨立的基於位置的參數 func(**name)        調用者    以name成對的傳遞所有的關鍵字/值,並作為獨立的關鍵字的參數 def func(name)        函數    常規參數:通過位置或變數名進行匹配 def func(name=value)    函數    默認參數值:如果沒有在調用中傳遞的話,就是用默認值 def func(*name)        函數    匹配並收集(在元組中)所有包含位置的參數 def func(**name)    函數    匹配並收集(在字典中)所有包含位置的參數。 5、關鍵字參數和默認參數的實例 >>> def f(a,b,c): …     print a,b,c … >>> f(1,3,4) 1 3 4 關鍵詞參數 >>> f(c=5,a=8,b=9)       8 9 5 >>> f(4,c=8,b=9)    4 9 8 >>> def func(name,age,job): …     print 'name','age','job' … >>> func('diege',18,'op') name age job >>> func(age=18,job='op',name='diege') name age job 關鍵字參數在調用中起到了數據標籤的作用 默認參數,如果調用時沒有傳入值得話,在函數運行前,參數就被賦了默認值。 >>> def f(a,b=2,c=5): …     print a,b,c … >>> f(4) 4 2 5 >>> def f(a,b=2,c=5): …     print a+b+c … >>> f(4) 11 >>> f(6,5)  16 >>> f(a=10) 17 >>> f(a=3,b=4) 12 >>> f(2,c=6)     10 >>> f(6,7,8) 21 6、任意參數的實例 *和** 讓函數支援接收任意數目的參數。 收集參數 第一種用法:在函數定義中,在元組中收集不匹配的位置參數 >>> def f(*args):print args … >>> f() () **特性類似,但是它只對關鍵字參數有效。將這些關鍵字參數傳遞給一個新的字典。 >>> def f(**agrs):print args >>> f() >>> def f(**args):print args … >>> f() {} min調用 def min1(*args):     res=args[0]     for arg in args[1:]:         if arg < res:             res=arg     return 三、函數的高級話題 (一)、匿名函數:lamdba lambad 創建了一個之後能夠被調用的函數,它返回了一個函數而不是將這個函數賦值給一個變數名。 1、lambda表達式 lanbda arg1,arg2,…,argN:expression using arguments lambda 是一個表達式,而不是一個語句 lambda的主體是一個單個的表達式,而不是程式碼塊 >>> def func(x,y,z):return x+y+z … >>> func(2,3,4) 9 等同於 >>> f=lambda x,y,z:x+y+z >>> f(2,3,4) 9 默認參數也能夠再lambda參數中使用,就像在def中使用一樣。 >>> x=(lambda a='free',b='file',c='feel':a+b+c)   >>> x('well') 'wellfilefeel 為什麼要使用lambda lambda 通常用來編寫跳轉表,也就是行為的列表或字典,能夠按照需要執行相應的動作。 L=[(lambda x:x**2),(lambda x:x**3),(lambda x:x**4)] >>> for f in L: …     print f(2) … 4 8 16 >>> print L[0](3) 9 2、嵌套lambda和作用域 >>> def action(x): …     return (lambda y:x+y) … >>> act=action(99) >>> act <function <lambda> at 0x285066f4> >>> act(2) 101 (二) 作參數來應用函數 1、內置函數apply 當需要變得更加動態的話,可以通過將一個函數作為一個參數傳遞給apply來調用一個生成的函數,並且也將傳給那個函數的參數作為一個元組傳遞給apply函數() 2、傳入關鍵字參數 >>> echo(1,2,a=3,b=4) (1, 2) {'a': 3, 'b': 4} >>> arg1=(1, 2) >>> arg2={'a': 3, 'b': 4} >>> apply(echo,arg1,arg2) (1, 2) {'a': 3, 'b': 4} 3、和apply類似的調用語法 >>> apply(func,args) 9 >>> func(args) 9 >>> apply(echo,arg1,arg2) (1, 2) {'a': 3, 'b': 4} (三) 在序列中映射函數:map >>> L=[1,2,3,4,5] >>> updated=[] >>> for x in L:            …     updated.append(x+10) … >>> updated [11, 12, 13, 14, 15] 使用內置工具map,map函數會對一個序列對象中的每一個元素應用被傳入的函數,並且返回一個包含了所有函數調用結果的一個列表。 >>> def inc(x):return x+10 … >>> L=[1,2,3,4,5] >>> map(inc,L) [11, 12, 13, 14, 15] map(函數,傳入函數的序列對象) >>> map(None,S1,S2) >>> L=[1,2,3,4,5]  map嵌套lambda       >>> map((lambda x:x+3),L) [4, 5, 6, 7, 8] 高級功能:提供了多個序列作為參數,它能夠並行返回分別以每個序列的元素作為【函數對應參數】得到的結果的列表 >>> pow(3,4) 81 >>> map(pow,[1,2,3],[2,3,4])    #1**2,2**3,3**4 [1, 8, 81] (四) 函數式編程工具:filter和reduce 函數式編程的意思就是對序列應用一些函數的工具。 基於某一測試函數過濾出一些元素-filter 對每對元素都應用函數並運行到最後結果-reduce >>> range(-5,5) [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4] >>> filter((lambda x:x>0),range(-5,5)) [1, 2, 3, 4] 這個等效於for range:if語句 reduce稍微複雜一點。這裡兩個reduce調用,計算在一個列表中所有元素加起來和以及乘起來的乘積 >>> reduce((lambda x,y:x+y),[1,2,3,4]) 10 >>> reduce((lambda x,y:x*y),[1,2,3,4]) 24 (五)重訪列表解析:映射 1、列表解析基礎 >>> [x**2 for x in range(10)] [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] >>> map((lambda x:x**2),range(10)) [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 2、增加測試和嵌套循環 在for之後編寫if分支,用來增加邏輯選擇,if分支相當filter >>> [x for x in range(5) if x%2==0] [0, 2, 4] >>> filter((lambda x:x%2==0),range(5)) [0, 2, 4] filter出來的列表可以作為map的第2個參數 >>> map((lambda x:x**2),filter((lambda x:x%2==0),range(5))) [0, 4, 16] 3、列表解析和矩陣 4、理解列表解析 對ptyhon初學者,通常使用簡單的for循環,在其他大多數情況下,使用map調用(除非它們會變得過於複雜) 列表解析比map快,map比for循環快2倍 (六)重訪迭代器:生成器 編寫的函數能夠返回一個值,並且稍後還可以從它剛才離開的地方仍然返回值。這樣的函數被認作是生成器,因為它們隨時間生成一個序列的值。 大多數方面生成器函數就像一般函數,在Python它們被自動用作實現迭代協議,因它只能夠再迭代的語境中出現。 生成器和一般的函數之間程式碼上最大的不同就是一個生成器yield一直,而不是return一個值。yield語句將會將函數關起,並向它的調用者返回一個值 但是保存足夠的狀態資訊為了讓其能夠在函數從它掛起的地方恢復。 包含yield的語句的函數將會特地編譯成為生成器。當調用它時,他們返回一個生成器對象,這個生成器對象支援迭代器對象介面。 1、生成器實例的。 >>> def Dtest(N): …     for i in range(N): …             yield i**2 使用 >>> for i in Dtest(5): …     print i,':', … 0 : 1 : 4 : 9 : 16 : 查看過程 >>> x=Dtest(4) >>> x.next() 0 >>> x.next() 1 >>> x.next() 4 >>> x.next() 9 >>> x.next() for循環和生成器是一樣的:通過重複調用next方法,知道捕獲一個異常。 2、擴展生成器協議send和next 生成器函數協議中增加了一個send方法,send方法生成一系列結果的下一個元素,這一點就像next方法一樣,但是它也提供了 一種調用者與生成器之間的進行通訊的方法,從而能偶影響它的操作。 yiled是一個表達式,可以返回傳入的元素來發送,而不是一個語句。值是通過調用本身send(value)方法傳給生成器的。 之後恢復生成器的程式碼,並且yield表達式返回了為了發送而傳入的值。如果調用了正常的放next()方法,yield返回None 3、迭代器和內置類型 內置的數據類型設計了對應於內置函數iter的迭代器對象。字典迭代器在每次迭代中產生關鍵字列表元素。 >>> D={'a':1,'b':2,'c':3} >>> x=inter(D) >>> x.next()  'a' >>> x.next() 'c' >>> x.next() 'b' >>> x.next() Traceback (most recent call last):   File "<stdin>", 所有迭代內容(包括for循環,map調用,列表解析等)一次設計用來自動調用iter函數。 通過支援迭代協議的類來實現任意的生成器對象是可能的,並且已經有很多這樣的對象,在for循環和其他的迭代環境中使用。 這樣的類定義了一個特別的__iter__方法,它將返回一個迭代對象。 4、生成器表達式:迭代器遇到列表解析 迭代器和列表解析的概念形成了這個語言的一個新的特性,生成器表達式。 (八)函數設計概念 *耦合性:對於輸入使用參數,並且對於輸出使用return語句 *耦合性:只有在真正必要的情況下使用全局變數。 *耦合性:不要改變可變類型的參數,除非調用者希望這樣做。 *聚合性:每一個函數都應該有一個單一的,同一的目標 *大小:每一個函數應該相對較小。 *耦合:避免直接改變在另一個模組文件中的變數。 函數是對象:簡潔調用 (九)函數陷阱 1、本地變數是靜態檢測的 >>> X=99 >>> def selector(): >>> def selector(): …     import __main__ …     print __main__.X …     X=88 …     print X … >>> selector() 99 88 >>> X 99 所有的全局變數都再 __main__。 __main__就是頂層的命名空間.__main__.X得到了全局變數版本的X。 這裡使用模組的屬性標記來獲得其全局變數。 2、默認和可變對象。 默認參數是在def語句運行時被評估並保存的,而不是在這個函數調用時。從內部來將,Python會將每一個默認 參數保存成一個對象。附加在這個函數本身。 3、沒有return語句的函數 函數中return和yield語句是可選擇。沒有返回值函數自動返回None對象。 這樣的函數被當做語句,就像他們只執行任務而不要計算有用的結果一樣。 4、嵌套作用域的循環變數