Python 學習 第七篇:函數1(定義、調用和變量的做用域)

函數是把一些語句集合在一塊兒的程序結構,用於把複雜的流程細分紅不一樣的組件,可以減小代碼的冗餘、代碼的複用和修改代碼的代價。html

函數能夠0個、1個或多個參數,向函數傳遞參數,能夠控制函數的流程。函數還能夠返回代碼執行的結果,從技術上講,任何函數都要返回結果,一個沒有返回值的函數會自動返回none對象。若是調用者須要函數返回結果,須要顯式使用return語句。編程

一,函數的定義

Python使用def 語句將建立一個函數對象,並將其賦值給一個變量名,()內是函數的參數,參數經過賦值向參數傳值。app

def fun_name (arg1,age2...): fun_body

return語句表示函數調用的結束,並把結果傳遞給調用者。return語句是可選的,若是它沒有出現,那麼函數將會在控制流執行完函數主體時結束。函數

1,def是可執行的語句測試

def語句是實時執行的,不只def語句,Python中的全部語句都是實時運行的,並無獨立的編譯時間。def語句是一個可執行的語句,在def執行前,函數並不存在,直到def語句執行以後,函數才被建立。ui

2,函數是一個對象,函數名是變量spa

def語句是一個賦值語句,函數是一個對象,函數名是一個變量,def語句設置函數名和函數對象的引用。code

當def語句運行的時候,它建立了一個新的函數對象,並將其賦值給一個變量名。htm

3,參數是經過賦值傳遞的對象

Python經過賦值(=)把參數傳遞給函數,這和普通的賦值語句(a=1,或a=b)的行爲是相同。當傳遞常量給參數時,參數引用的是一個全新的對象;當傳遞變量給參數,參數和變量是對象的共享引用。

4,函數是能夠嵌套的

一個def是一個語句,能夠出如今任一語句能夠出現的地方,甚至嵌套在其餘的語句中。這就是說,函數內部能夠嵌套函數的定義,例如:

def times(x,y): z=x*y def print_result(): print(z) print_result()

函數 print_result()是一個嵌入函數,定義在函數times(x,y)以內。

實際上,Python的函數是有層次結構,最外層的def是頂層函數,在頂層函數內部定義的函數是嵌套函數。

二,函數的調用 

函數經過表達式調用,傳入一些值,並返回結果。函數調用的格式是:函數名 + (args),小括號中是傳遞給參數的變量或值,例如:

func_name(var1,var2...)

若是函數存在返回值,使用變量來接收函數的返回值:

ret_value=fun_name(var1,var2...)

在調用函數時,函數的行爲依賴於類型,這是由於函數是語句的集合,語句包含操做符,而操做符的行爲是依賴於類型的,

例如,函數times返回兩個參數的乘積,當傳入數字時,函數 times(2,4) 返回8,* 的做用是計算乘積;當傳入字符類型時,函數 times('ab',2) 返回'abab',*號的做用是重複字符串。換句話說,函數times()的做用取決於傳遞給它的值。

def times(x,y): return x *y

調用函數時,這種依賴於類型的行爲稱爲多態,就是說,一個操做的做用取決於操做對象的類型。函數的多態性,使得函數能夠自動適用於全部類別的對象類型。

若是傳給函數的對象有預期的方法和表達式操做符,那麼函數就兼容對象。若是傳遞的對象不支持預期的接口,Python會在 * 表達式運行時檢測到錯誤,並自動拋出一個異常。

這種特性,使得Python代碼不該該關心特定的類型,函數應該爲對象編寫接口,而不是數據類型。固然,這種多態的編程模型意味着:必須測試代碼去檢測執行結果是否錯誤,而不是編寫代碼進行類型檢查。

三,變量的做用域

在Python代碼中變量無處不在,命名空間就是保存變量名的地方,變量名可以訪問(可見)的命名空間叫作做用域。

當在程序中使用變量名時,Python建立、改變或者查找變量都是在命名空間中進行的,變量名被賦值的位置決定了變量名可以被訪問的範圍。

1,變量的分類

Python中的變量在第一次被賦值時建立,而且必須通過賦值後才能使用。因爲沒有變量的聲明,Python把變量名被賦值的地點關聯爲(綁定爲)一個特定的命名空間。

根據變量的命名空間,把變量大體分爲三類:

  • 模塊是全局命名空間,其名稱是模塊文件的名稱,位於模塊內的頂層函數名和變量名叫作全局變量。
  • 函數是局部命名空間,其名稱是函數的名稱,位於函數內的函數名和變量名叫作本地變量。
  • 因爲一個函數能夠嵌套在其餘函數內,這使得函數的定義具備層次。咱們把一個不在本函數內定義的、而是在上層函數中定義的本地變量叫作非本地變量,也就是,這個變量不是當前函數的本地變量,而是上層函數的本地變量。

2,變量的做用域

變量的做用域是指變量可見的範圍,一個變量的做用域老是由變量被賦值的地方決定的,也就是說,變量被賦值的地方決定了變量可見的範圍。

  • 若是一個變量賦值的地點是在def以內,那麼該變量是本地變量,做用域在def以內,在def以外,本地變量是不可見的。
  • 若是一個變量賦值的地點是在def以外,那麼該變量是全局變量,做用域是全局的,在函數內能夠引用全局變量。

在默認狀況下,一個函數的全部變量名都是與函數的命名空間相關聯的:

  • 一個在def內定義的變量名只能被def的代碼使用,不能在函數的外部調用該變量名;
  • def中的變量名與def以外的變量名並不衝突,一個在def以外被賦值的變量x和在def中被賦值的變量x是不一樣的變量。

因爲變量能夠在三個地方分配,那麼變量的做用域實際上分爲三類:

  • 若是一個變量是在def以內賦值,變量可見範圍是在函數內,變量的做用域是本地(local);
  • 若是一個變量是在def以內賦值,對該函數中嵌套的函數來講,該變量的做用域是非本地的(nonlocal),或稱做嵌套(enclosed)做用域;
  • 若是一個變量是在def以外賦值,變量可見的範圍是整個模塊,變量的做用域是全局(global)。

3,做用域法則

全部變量名均可以概括爲內置的(builtin)、全局的(global)和本地的(local)。

內置的模塊是Python預先定義好,能夠直接引用的。

模塊定義的是全局做用域,全局做用域的做用範圍僅限於單個模塊(文件),也就是說,在一個文件的頂層的變量名對於這個文件內部的代碼而言是全局的。

在默認狀況下,在函數內部,賦值的變量名除非聲明爲全局變量或非本地變量以外,都是本地做用域內的。函數還定義了嵌套的做用域,使其內部使用的變量名本地化,以便函數內部使用的變量名不會與函數外的變量名衝突。每次對函數的調用都會建立一個新的本地做用域。若是須要給一個嵌套的def中的變量名賦值,從Python 3.0開始,可使用 nonlocal語句聲明來作到。

注意:模塊頂層的函數名是全局變量,函數內部的def定義的是局部變量;函數的參數是本地變量;一個函數內部的任何類型的賦值都會把一個變量劃定爲本地的,這意味着,函數內部的賦值(=)語句,def語句等,定義的都是本地變量。

4,變量名解析(LEGB原則)

變量名的解析聽從LEGB原則,當引用一個變量時,Python按照如下順序依次進行查找:從本地變量中、在任意上層函數的做用域、在全局做用域,最後在內置做用域中查找。

LEGB法則解析變量名的詳細機制:

  • 當在函數中引用變量名時,Python依次搜索4個做用域:本地做用域(L),而後是上一層結構中def的本地做用域(E),再而後是全局做用域(G),最後是內置做用域(B),而且再第一處可以找到該變量名的地方停下來。若是變量名再此次搜索中沒有找到,Python會報錯。
  • 當在函數中給一個變量名賦值時(而不是在一個表達式中對其進行引用),Python老是建立或改變本地做用域的變量名,除非它已經在當前函數中聲明爲全局變量(global)或者非本地變量(nonlocal)。

把嵌套做用域定義爲:在當前的def語句以外,在頂層def語句以內的做用域,嵌套做用域的解析細節:

  • 對變量x的引用,首先在當前函數內查找變量名x;以後會向上層的函數中查找變量名x,從內向外依次查找嵌套做用域;以後查找當前的全局做用域(模塊);最後再到內置做用域內查找。而全局聲明將會直接從全局做用域(模塊)進行搜索。
  • 對變量x的賦值,若是變量x在函數內部聲明爲全局變量(global x),那麼賦值會修改全局變量x的值;若是變量x在函數內被聲明爲非本地變量(nonlocal x),那麼賦值會修改最近的嵌套函數的本地做用域內的變量x的值。

5,在函數內引用全局變量

global不是聲明一個類型,而是聲明命名的命名空間是全局的,也就是說,告訴函數打算聲明一個或多個全局變量名。

對於全局變量名,這裏對用法做一個總結:

  • 全局變量是位於模塊內部頂層的變量名;
  • 若是要在函數內對全局變量進行賦值,那麼必須聲明該變量是全局的;
  • 全局變量名在函數的內部能夠直接引用。

例如,x是全局變量,在函數func中使用global 聲明x是全局變量,對x賦值,就是修改全局變量的值:

x=11
def func(): global x x=12

使用global語句把變量聲明爲全局變量,這樣,在函數內部就能夠修改全局變量的值,也就是說,global語句容許在def中修改全局變量的值。

global語句包含了關鍵字global,其後跟着一個或多個由逗號分開的變量名,當在函數內被賦值或引用時,全部列出來的變量都被映射到全局變量名。

global x,y,z

6,在函數內引用上層的非本地變量

Pytho 3.0 引入了nonlocal語句,用於在一個函數內聲明一個非本地的變量,該變量定義於一個def語句中,而且位於嵌套做用域的上層。

例如,函數foo1定義了變量var1和var2,要想在函數foo2中改變它們的值,必須在foo2中使用nonlocal語句把它們聲明爲非本地變量:

def foo1: var1=1 var2=2 ... def foo2: nonlocal var1,var2,.. 

nonlocal語句是一個聲明語句,用於把函數內的變量聲明爲非本地變量。非本地變量是指不在本函數內定義的,而是在上層函數中定義的本地變量。

nonlocal語句的用法解析:

  • nonlocal語句徹底忽略當前函數的本地做用域,這意味着,nonlocal語句使得對該語句列出的名稱的查找從上層函數的做用域開始,而不是從語句聲明的本地做用域開始。
  • nonlocal語句列出的名稱,必須在一個嵌套的def中提早定義過,不然,Python將會產生一個錯誤,也就是說,nonlocal語句聲明的變量只能是def中定義的本地變量,而不能是模塊的全局變量。
  • nonlocal語句容許對非本地變量賦值,修改其值。
  • 在內嵌的函數中,能夠直接引用非本地變量,不須要使用nonlocal語句聲明。

nonlocal語句提供了一種方式,使得嵌套的函數可以提供可寫的狀態信息,以便在隨後調用嵌套的函數時,可以記住這些信息。簡而言之,nonlocal語句的引入使得Python容許修改非本地變量。

四,閉合函數

Python的閉合函數是指一個可以記住嵌套做用域的變量值的函數,儘管那個做用域已經不存在了。

例如,建立一個閉合函數maker,該函數生成並返回一個嵌套函數action,卻並調用這個內嵌的函數。

def maker(x): def action(y): return x*y return action

調用閉合函數,獲得的是生成的內嵌函數的一個引用。當咱們調用閉合函數,它會返回內嵌函數的引用;當調用內嵌函數action時,咱們發現儘管閉合函數已經返回並退出,可是,內嵌函數記住了閉合函數內部的變量x的值。

f=maker(2) f(3)

也就是說,閉合函數的本地做用域的信息被保留了下來。爲了可以在內嵌的def中使用變量x的值,Python自動記住了所須要的上層做用域的任意值。

注意:若是lambda或者def在函數中定義,嵌套在一個循環之中,而且嵌套的函數引用了一個上層做用域的變量,該變量被循環所改變,全部在這個循環中產生的函數將會由相同的值——在最後一次循環中完成時被引用變量的值。 

>>> def maker(): ... acts=[] ... for i in range(5): ... acts.append(lambda x:i**x) ... return acts ... >>> acts=maker() >>> acts[0](2) 16

由於嵌套做用域中的變量在嵌套的函數被調用時才進行檢查,因此,它們實際上記住的是一樣的值(在最後一次循環迭代中循環變量的值)。

也就是說,只有當調用acts[0](2)時,採起檢查變量i的值,此時變i的值是最後一次迭代的值4。要解決這類問題,必須在函數maker調用時,對i的值進行評估,並保存起來。

>>> def maker(): ... acts=[] ... for i in range(5): ... acts.append(lambda x, i=i : i**x) ... return acts

爲了讓這類代碼可以工做,必須使用默認參數把當前的值傳遞給嵌套做用域的變量。由於默認參數是在嵌套函數建立時評估的(而不是其稍後調用時),因此,每個函數都記住了本身的變量 i 的值。

 

參考文檔:

原文出處:https://www.cnblogs.com/ljhdo/p/10104834.html

相關文章
相關標籤/搜索