函數是對程序邏輯進行結構化或過程化的一種編程方法。能將整塊代碼巧妙地隔離成易於管理 的小塊,把重複代碼放到函數中而不是進行大量的拷貝--這樣既能節省空間,也有助於保持一致性,由於你只需改變單個的拷貝而無須去尋找再修改大量複製代碼的拷貝。html
在C++裏我不記得有過程這種東西,可是在一些其它的語言好比PL/SQL裏面會有過程。過程和函數同樣是能夠調用的代碼塊,可是過程沒有返回值。在pyhon裏,面其實過程和函數是一個東西,由於若是你在函數裏面不指定返回值,python的interpreter會返回None.python
python的函數會返回一個返回值。 若是你顯示的指定了這個值,那麼返回的就是你想要的類型,不然python的interpreter默認返回none。好比下面的例子就是:c++
>>> def foo(): ... pass ... >>> print a None
可是,有人聲稱python的函數能夠返回多個值。其實事實是,python把這些返回值打包到了一個元組中。好比下面的例子:編程
>>> def foo(): ... return 1,2,'a','b' ... >>> foo() (1, 2, 'a', 'b')
python 能夠經過返回一個容器,好比 list ,dict,tuple等來間接的實現返回多個值的目的,上面的例子中返回的是一個元組,由於元組的語法不強制要求寫括號,因此 return語句就把後面逗號分隔的內容當作一個元組一塊兒返回。數組
關鍵字參數指的是以下調用函數的形式,能夠看到在函數調用的時候採用的是(x= , y= )。這樣的形式。這種理念是讓調用者經過函數調用中的參數名字來區分參數。這樣規範容許參不按順序,由於解釋器能經過給出的關鍵字來匹配參數的值。閉包
>>> def foo(x,y): ... print x,y ... >>> foo(y=2,x=1) 1 2
但要注意的是關鍵字參數的概念僅僅是針對函數調用來講的,也就是說在定義函數的時候不用作任何的特別設定。咱們再看一個例子,假設如今有一個函數conn()須要兩個參數 host 和 port。 定義以下:app
>>> def conn(host,port): ... hostname=host ... portnumber=port ... print hostname,portnumber
你在調用的時候能夠採用正常的調用方式,按順序輸入恰當的參數,如:ide
>>> conn('server_A',22) server_A 22
你也能夠用關鍵字調用的方式,這時候就不用管輸入參數的順序了,如:函數式編程
>>> conn(port=22,host='server_B') server_B 22
這個很簡單,在c/c++裏都有,就是在函數定義的時候提供一個默認的參數,這樣將來調用的時候若是不顯示提供參數,函數就會使用默認的參數。函數
>>> def foo(x=4,y=4): ... return x+y ... >>> foo() 8
python的函數在調用時,能夠把參數放入一個元組或者是字典,而後把這個元組或者字典傳遞給函數進行調用。固然元組中存放的是非關鍵字參數而字典中存放的是關鍵字參數。
>>> def foo(x,y): ... return x+y ... >>> a=4,4 >>> foo(*a) 8 >>> d={'y':5,'x':3} >>> foo(**d) 8
注意在元組前加一個* 而在字典前要加**。他們表示把字典或者元組解開的意思,也就是把a還原成 4,4 而 d還原成 y=3,x=5
建立函數的語法很是簡單,以下:
def function_name(arguments): "function documentation string" function body
文檔字符串是可選的。
有些語言好比C/C++中,函數的聲明和定義是分開的。聲明只包括函數的名字和參數列表,而函數的定義則要包括函數的內部邏輯。函數聲明和定義有區別的語言每每是由於他們要把函數聲明和函數定義放到不一樣的文件裏,而在python裏面二者是一體的。
狀況下面一段代碼:
def foo(): print "in foo()" bar() def bar(): print "in bar()" foo()
若是放在python解釋器中運行這段代碼,也許你會認爲會出錯,由於在調用bar()函數的時候,bar函數尚未定義。 但實際上不會出錯,由於雖然foo中bar調用在bar的定義以前,可是foo()自己的調用並非在bar的定義以前
python中函數其實也是一個實例或者說對象。而python中的對象能夠當作一個名稱空間。換個說法就是python中的對象,好比這裏的函數,能夠用來存儲名字。看一下下面的實例那就明白了:
>>> def foo(): ... 'this is the doc string of foo' ... >>> foo.attr1=1 >>> foo.attr2=2 >>> foo.attr1 1 >>> foo.attr2 2
咱們建立了一個函數,這個函數什麼都沒作,而後咱們用句點符號來爲函數建立了兩個屬性attr1,attr2而且賦值,這樣咱們就能夠經過句點符號來訪問這兩個屬性了。 關於名稱空間的概念後面會有詳細介紹,這裏咱們只要理解爲一個存儲名字的空間就能夠了。要注意的另一點是,上面例子中在定義foo的時候建立了一個文檔字符串。所謂文檔字符串就是函數裏面第一個沒有賦給變量的字符串,這個字符串能夠經過 函數名.__doc__來訪問,也能夠經過help來訪問
>>> help(foo) Help on function foo in module __main__: foo() this is the doc string of foo >>> foo.__doc__ 'this is the doc string of foo'
內嵌函數就是在函數體的內部建立的一個函數,好比下面的代碼:
>>> def foo(): ... print 'foo is called' ... def bar(): ... print 'bar is called' ... bar() ... >>> foo() foo is called bar is called
但要注意的是,內嵌函數的做用域只能是在外部函數內,因此下面的代碼就會出錯
>>> bar() Traceback (most recent call last): File "<stdin>", line 1, in ? NameError: name 'bar' is not defined >>>
由於你調用bar的地方是在foo的函數體外部,這裏識別不到bar。
裝飾器是python中的一個比較獨特的東西,至少在c/c++中是沒有這個東西的。 咱們首先看一下python的裝飾器大概是什麼樣的,若是感受困惑,咱們後面的連接中提供了一篇文章,能夠很清晰的闡述裝飾器的意義已經它究竟是什麼。
首先,python中使用裝飾器的代碼大概是下面這樣的
>>> @decorator ... def foo(): ... pass ...
上面代碼中的decorator就是一個裝飾器,用@放在它的前面就表示它是裝飾器,而上面代碼的意思就是,咱們將要用 decorator 這個裝飾器,來修飾一下咱們的foo函數,修飾完成以後,你再調用foo的時候,foo其實就已經變成了另一個函數,上面代碼等同於 foo=decorator(foo).
若是你仍是感受困惑,能夠看一下這個連接 http://www.cnblogs.com/kramer/p/3640040.html
有時候咱們須要處理可變數量參數的狀況。 也就是說直到調用的時候 纔會知道函數須要多少個參數。 這在python中是可以作到的。 不過,咱們知道python中調用函數的時候分爲兩種狀況,非關鍵字參數和關鍵字參數,相對應的,若是要實現可變長參數,也要分兩種狀況考慮--可變長的非關鍵字參數和可變長的關鍵字參數。首先看可變長的非關鍵字參數。 好比下面的代碼,
>>> def foo(arg1,arg2=2,*restArgs): ... print arg1 ... print arg2 ... print restArgs ...
上面的函數定義中,除了形參 arg1,arg2=2以外,還有一共*restArgs。 最後這個形參的意思就是,若是 foo在調用的時候有大於等於3個非關鍵字參數,那麼除了第一個要賦給arg1,第二個要賦給 arg2以外,剩下的要組成一個元組賦給 restArgs。 咱們能夠看一下,運行時的狀況:
>>> foo(1,2,3,4,5,6,7,8) 1 2 (3, 4, 5, 6, 7, 8) >>> foo(1) 1 2 ()
第一次調用 除了第一個和第二個參數,剩下的都被賦給了 restArgs,而第二次調用,因爲只有一個參數,因此它賦給了arg1,arg2採用了默認參數,而restArgs是一個空的元組。
上面是針對非關鍵字參數的狀況,那麼可變長的關鍵字參數,怎麼處理呢?
>>> def foo(arg1,arg2,**restKeyArgs): ... print arg1 ... print arg2 ... print restKeyArgs ...
上面這段代碼的意思是,在調用foo的時候,除了第一個和第二個參數,剩下全部的關鍵字參數都要複製給 restKeyArgs做爲一個字典對象。好比在調用的時候:
>>> foo(1,2,x=3,y=4) 1 2 {'y': 4, 'x': 3}
1,2賦值給了arg1,arg2. x=3和y=4複製給了restKeyArgs做爲一個字典對象。
若是你想讓你的函數在調用的時候,能夠接受任意個非關鍵字參數和關鍵字參數,你能夠這樣定義函數:
>>> def foo(arg1,arg2,*restNonkey,**restKey): ... print arg1 ... print arg2 ... print restNonkey ... print restKey ... >>> foo(1,2,3,4,5,x=1,y=2) 1 2 (3, 4, 5) {'y': 2, 'x': 1}
總結一下,就是說若是你想讓函數接受可變長的非關鍵字參數,就使用一個帶有一個*的形參來接受可變長個實參 , 若是你想讓函數接受可變長的 關鍵字參數 ,那麼就用一個帶有兩個*的形參來接受可變長個關鍵字實參。
可是有一個地方要明白,咱們以前提到了參數組。所謂參數組就是能夠在調用函數的時候把參數打包進容器(非關鍵字打包進元組,而關鍵字參數打包進字典),而後在調用的時候經過*或者**來表名他們是關鍵字參數或者是非關鍵字參數。雖然都用到了*和**這個符號,但跟咱們這一小節講的可變長參數雖然很像是有區別的。由於這一小節的知識是在定義函數的時候讓函數能夠有可變長個參數,而參數組部分的知識是說咱們能夠把實參打包進容器傳遞給函數。 仔細看下面的例子:
>>> def foo(arg1,arg2,*restNK,**restK): ... print arg1 ... print arg2 ... print restNK ... print restK ...
這裏是咱們這一節講的知識點,可變長參數。而下面的調用涉及的* 和 **是參數組部分的知識點。
>>> t1 (1, 2) >>> d1 {'y': 2, 'x': 1} >>> foo(*t1,**d1) 1 2 () {'y': 2, 'x': 1}
函數是編程到底和麪向對象變成有什麼區別,這個我還不太清楚,不過關於python的函數是編程知識點能夠總結爲下面的內容。
匿名函數,顧名思義就是沒有名字的函數,而lambda就是定義匿名函數的一個方法。好比下面的代碼就是用lambda定義了一個匿名函數。
>>> lambda x:x*x <function <lambda> at 0x1c8e70> >>> type(lambda x:x*x) <type 'function'>
第一行代碼返回一個匿名函數,用type能夠驗證這一點。 並且能夠看出lambda定義匿名函數的語法很是簡單 lambda 後面緊跟形參列表,而後是冒號':'。 冒號後面是函數邏輯。簡單的說,第一行代碼就是定義了一個須要接受一個形參x,而後返回x的平方的一個函數。不過這個函數沒有名稱。整個代碼除了沒有一個函數名字外跟真正的函數沒什麼區別。
看到這裏你必定想問,那麼爲何咱們還須要匿名函數呢?看一下下面的場景。
假設你在寫程序的時候寫了下面一段代碼,
map(lambda x:x*x , [y for y in range(10)])
上面這段代碼使用了 lambda。邏輯很是清晰,就是說把 [y for y in range(10)]這個列表裏面的全部元素都平方一次,並返回一個新的列表。 但假設若是沒有lambda呢? 你須要這麼寫, 首先定義一個函數
>>> def a(x): ... return x*x ...
而後你要把這個函數應用到你的代碼裏面去,
map(a,[y for y in range(10)])
你覺的那種作法更好呢?是lambda仍是後面這種? 要知道,在實際狀況下, 你多是在面對幾萬條甚至更多的代碼。在閱讀代碼的時候,遇到上面的代碼,你就須要去查詢一下a這個函數是幹嗎的。這無疑是很耗費精力的。 若是這種狀況特別多,你就須要不斷的去查詢函數定義,而後返回來查看代碼,這是很頭疼的。 可是用第一種形式就至關的簡單明瞭。
因此lambda尤爲適用於這種邏輯簡單的函數。尤爲是,若是你這個函數僅僅定義且使用一次,那就更應該使用lambda了。 函數存在的意義是抽象而抽象的目的是提升代碼複用率。可是對這種僅僅使用一次的代碼,根本不涉及複用的問題,咱們爲何不直觀一點呢?
函數內部建立的變量,叫作局部變量,只在函數內部能夠訪問。而模塊內部最高層變量叫作全局變量。全局變量在模塊內任何一個地方都可以訪問。以下面的例子:
>>> g1=123 >>> >>> def foo(): ... l1=456 ... print 'g1 is ', g1 ... print 'l1 is ', l1 ... >>> foo() g1 is 123 l1 is 456 >>> g1 123 >>> l1 Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'l1' is not defined
首先咱們定義了一個全局變量g1,賦值爲 123。而後定義一個函數foo而且在函數內部定義局部變量l1賦值爲456. 咱們在foo內部會訪問如下g1和l1. 經過foo()函數調用能夠發現訪問g1和l1都沒問題。 可是在foo外部訪問g1能夠,訪問l1卻出錯。
雖說在模塊內部任何一個地方均可以訪問全局變量,但這麼說也不徹底正確。 python在訪問一個變量的時候是先從局部做用域找起,而後向外層做用域查找。若是這個過程當中找到了變量,python就中止查找。 因此若是你在局部做用域--好比說函數內部,定義了一個和全局變量同名的變量,那麼在這個局部做用域你可能就訪問不到全局變量了,由於python老是先找到你定義的局部變量,而後就中止查找了。好比:
>>> global_v=1234 >>> >>> def foo(): ... global_v=45679 ... ... >>> foo() >>> global_v 1234
這個例子中,咱們在foo內部,也就是局部做用域定義了一個和全局變量同名的變量global_v。因此在foo內部你就訪問不到全局變量global_v。因此你會發現雖然foo對global_v進行了從新賦值,可是真正的全局變量global_v並無改變。
不過你能夠用global 關鍵字來指定,你訪問的就是全局變量,以下:
>>> global_v=1234 >>> def foo(): ... global global_v ... global_v=45678 ... >>> foo() >>> global_v 45678 >>>
python中做用域是嵌套的,就是說內部做用域老是能訪問外部做用域中的變量。好比下面的代碼:
>>> def foo1(): ... l1='first' ... def foo2(): ... l2='second' ... def foo3(): ... l3='third' ... print 'l1 ', l1 ... print 'l2 ', l2 ... print 'l3 ', l3 ... foo3() ... foo2() ... >>> foo1() l1 first l2 second l3 third
foo3是第三層函數了,可是它能夠訪問全部外層變量
(這一小節參考博文 http://blog.csdn.net/marty_fu/article/details/7679297)python中的閉包簡單的說就是,若是在一個內部函數裏,對在外部做用域(但不是在全局做用域)的變量進行引用,那麼內部函數就被認爲是閉包(closure). 好比下面的例子:
>>> def foo(x): ... def inner(y): ... return x+y ... return inner ... >>> >>> inner=foo(2) >>> type(inner) <type 'function'> >>> inner(6) 8
inner是foo的內部函數,可是引用了foo的局部變量,因此就造成了閉包。 其實我最開始接觸閉包的時候還有一個疑問,拿這個例子來講,我認爲foo調用返回後,x這個局部變量就應該被銷燬了,因此inner調用應該會出錯纔對。 但實際上並非,由於python中的變量是每被引用一次 引用reference數會加1,每被銷燬或者刪除引用一次,reference數會減一。在這個例子中,foo引用了一次 inner又引用了一次,雖然foo退出了,可是inner沒有,因此x不會被消除。
a. python閉包修改外部做用域的時候,不要覆蓋外部變量
好比下面的例子,就是python的一個經典錯誤。
>>> def foo(): ... a=1 ... def bar(): ... a=a+1 ... return a ... return bar ... >>> >>> b=foo() >>> print b() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in bar UnboundLocalError: local variable 'a' referenced before assignment
這段代碼本意是在每次print b()的時候,都能遞加a一次。 可是仔細分析內部函數bar() 你就會發現問題所在。bar中a=a+1這一段,在python從左到右解讀的時候會認爲a=說明a是python的一個局部變量。 因此這裏a就覆蓋了foo中的a。可是緊接着你又使用a+1,這時候python就會認爲你在使用一個沒有賦值的局部變量,因此就出錯了。 解決辦法很簡單,就是把a變成一個容器。
>>> def foo(): ... a=[1] ... def bar(): ... a[0]=a[0]+1 ... return a[0] ... return bar ... >>> b=foo() >>> print b() 2
爲何python 看到a[0]的時候就不會認爲它是變量了呢? 我是這麼理解的,若是看到一個a=認爲它是變量,而後在本地先查找就找到了,因此就認爲是local變量。可是看到a[0]的時候,會首先認爲a是一個容器,而後就會在local查找,可是本地又沒有a的定義,因此就會到外層查找,而這樣就查到了外層的a。
ok,如今你應該會以爲上面的寫法有些彆扭吧?咱們的本意只是想讓閉包函數可以引用外層函數的變量foo而已,有必要弄出容器什麼的這麼奇怪嗎? python 3對這個就有了改進。在python 3中,你只要在 bar裏面 a的前面加一個nonlocal 聲明它是否是一個local變量就能夠了。這樣python就會跳過local 從外層開始查找,這樣就找到了 foo的a。
b. another error
咱們再看一個例子,這個例子可能和閉包沒太大關係,但也值得一看。 先看下面一段代碼:
>>> for i in range(3): ... print i ... 0 1 2 >>> print i 2 >>>
這段代碼自己只是很簡單的一段for循環,可是在python裏面for循環有個問題就是for循環結束以後不會銷燬它的內部變量 i.
python 還有一個問題就是python的函數體只有在執行的時候纔會肯定其函數體裏面變量的值。以下:
>>> funList=[] >>> for i in range(3): ... def foo(x): print x+i ... funList.append(foo) ... >>> for f in funList: ... f(2) ...
這一段代碼你也許會認爲執行的結果是2,3,4 但其實是4,4,4。這是由於python把這些函數放入funList的時候並無肯定i的值。
說了這麼多,閉包在函數編程中到底有什麼意義呢?主要有以下兩種做用:
a. 閉包執行完成後,保留住當前的運行環境。
以一個棋盤遊戲爲例,假設咱們有一個50*50的棋盤,左下角座標爲0,0 我須要一個函數,假設函數名爲 play,這個函數接受兩個參數,方向和步長,而後返回按照方向步長移動棋子後,棋子的新位置。固然這個函數還要記住我如今的位置,下一次在執行的時候還要從這個位置開始執行,這裏,就能夠用到閉包了,由於閉包能夠記住上一次執行結束的環境。
>>> orignal=[0,0] #orignal locaiton >>> def create(pos=orignal): ... def player(direction,step): ... pos[0]=pos[0]+direction[0]*step #x direction ... pos[1]=pos[1]+direction[1]*step #y direction ... return pos ... return player ... >>> p=create() >>> print p([1,0],2) #x direction 2 steps [2, 0] >>> print p([1,0],3) #x direction 3 steps [5, 0] >>> print p([0,1],3) #y direction 3 steps [5, 3] >>> print p([1,0],-2)#x direction -2 steps [3, 3] >>> print p([0,1],2) #y direction 2 steps [3, 5]
b. 閉包函數能夠根據外部做用域的局部變量來獲得不一樣的結果
這就相似於配置的意思。用外部函數的局部變量來配置內部閉包,看完下面的例子你就明白了。
假設咱們須要對一些文件進行過濾處理, 過濾出含有某些詞的行, 看下面的代碼:
>>> def makeFilter(word): ... def filter(file): ... f=open(file) ... lines=f.readlines() ... f.close ... results=[i for i in lines if word in i] ... return results ... return filter ... >>> >>> myfilter=makeFilter('host') >>> myfilter('/tmp/a.txt')
這個閉包中,外部函數起到了一個配置的做用,經過外部函數能夠建立不一樣的filter。
理解生成器以前,須要瞭解另外一個概念迭代器。迭代器是python中一種線性的數據集合,有一個next()方法。調用next() 會依次序返回迭代器中的數據,當訪問完迭代器中最後一個數據以後,會拋出預約義的異常 StopIteration。 python中線性的數據集合有不少,好比list tuple string,但他們沒有next()方法,因此都不是迭代器,不過咱們能夠用迭代器的工廠函數iter()來把這些類型變成迭代器。以下:
>>> a=(1,2,3,4,5,6,7,8) >>> a1=iter(a) >>> a1.next() 1 >>> a1.next() 2
具體能夠參考這篇文章。http://www.cnblogs.com/kramer/p/3678879.html
python中使用了yield關鍵字的函數叫作生成器函數。普通的函數返回一個返回值,可是生成器函數卻返回一個生成器。 好比下面的代碼:
>>> def foo(): ... print 'begin run' ... yield 1 ... yield 2 ... yield 3 ... >>> a=foo() >>> type(a) <type 'generator'>
使用了yield的函數foo 在 a=foo()的時候 並無執行print語句,而咱們用type(a)能夠看到,foo()返回的是一個叫作生成器的東西。 接下來,咱們要運行a.next(),函數foo的內部邏輯纔會真的開始執行,但每次調用只會調用到一個yield關鍵字處,而後再次調用會調用到接下來的yield關鍵字處。最後一個yield返回後,咱們再調用a.next()會像迭代器同樣拋出StopIteration異常。
>>> a.next() begin run 1 >>> a.next() 2 >>> a.next() 3 >>> a.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
因此,你能夠發現,生成器和迭代器很像,惟一不一樣的就是,生成器裏面可能會有一些函數邏輯,好比這裏的print語句。
python 2.5以後對yield作了一些調整。原來的yield是關鍵字或者說是語句,但如今是表達式了。 也就是說原來的 yield 5只是表明某次迭代的時候會返回5.但如今 yield 5會有返回值了,由於yield是一個表達式。 好比下面的代碼:
>>> def foo(): ... print 'step 1' ... a1=yield 1 ... print 'a1 is ', a1 ... a2=yield 2 ... print 'a2 is ', a2 ... a3=yield 3 ... print 'a3 is ', a3 ...
在你調用 b=foo()會生成一個生成器,接下來你調用b.next()會執行到第一個yield 處並暫停
>>> b=foo() >>> b.next() step 1 1
接下來就是yield 跟之前不一樣的地方了。 咱們知道yield如今是表達式了。a1=yield 1不但會打印出一個1,還應該返回一個值給a1。 那麼這個值是什麼呢? 這個值是一個叫send的函數傳遞進去的。 新版本的python有了一個send函數,這個函數的做用是接受一個參數,並把這個參數當作是yield 表達式的當前返回值。 因此咱們接下來的調用是這樣的。
>>> b.send('two') a1 is two 2
咱們調用b.send('two')。 send這時候會把two當作當時yield的返回值 傳遞給a1.而後生成器繼續往下走走到第二個yield 表達式處。 如今你明白send的用法了吧。 ok 其實next()和send(None)是等價的,因此咱們能夠理解爲next()就是發送一個None給當時的yield當作返回值而後繼續往下執行,那麼咱們看一下是否是這樣的
>>> b.next() a2 is None 3
正是咱們所指望的