Python基础(五)--函数
目錄
?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Python基礎(五)--函數
1 函數的作用
1.1 函數定義與調用
1.2 函數的作用
1.3 空語句
2 參數與返回值
2.1 函數的參數
2.2 函數的返回值
2.3 返回多個值
3 參數的默認值
3.1 可選參數
3.2 參數的默認值
4 位置參數與關鍵字參數
4.1 關鍵詞做參數
5 可變參數
5.1 兩種可變參數
5.2 打包與拆包
6 參數傳遞
7 命名空間與作用域
7.1 命名空間
7.2 作用域
7.3 LEGB原則
8 Lamabda表達式
9 遞歸
9.1 什么是遞歸
9.2 循環與遞歸
9.3 遞歸的深度
10 高階函數
11 函數描述
11.1 函數說明文檔
11.2 函數注解
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Python基礎(五)--函數
1 函數的作用
1.1 函數定義與調用
函數的概念:函數是具有一定功能的代碼塊,該代碼塊可以重復的執行。
函數的定義方式如下:
? ? def 函數名(參數列表):
? ? ? ? ?函數體
函數的調用:當定義了一個函數即定義了一個功能后,我們使用該功能就稱之為函數的調用。當調用函數時,程序會跳轉到函數定義處執行函數體,當函數體執行完畢后程序流程會返回給函數的調用端,繼續執行調用后的語句。
# 函數的定義 def print_triangle():for i in range(9):for j in range(i+1,9):print("*",end=" ")print() # 函數的調用 print_triangle()1.2 函數的作用
(1)避免代碼重復,實現代碼的復用
(2)對功能進行詳細的劃分,在需要使用功能時,就可以調用該函數。
1.3 空語句
在程序設計時,我們就可以先把程序劃分成若干個功能,具體的實現可以后續再完成。此時,我們就可以使用pass來進行占位。pass表示空語句,用在函數中用來進行占位。pass也可以放在選擇與循環體中。
# 空函數 def vacancy():pass2 參數與返回值
2.1 函數的參數
在函數定義時提供的參數,稱為形式參數;在函數調用是提供的參數稱為實參;在函數調用的過程中,實際參數會賦值給形式參數。
參數的作用:功能是由函數本身所決定的,但是功能具有一定的細節,具體的細節通過參數進行調整控制。
# 函數的定義 def print_triangle(length):for i in range(length+1):for j in range(i+1,length+1):print("*",end=" ")print() # 函數的調用 print_triangle(2) print_triangle(3)2.2 函數的返回值
函數也是可以具有返回值,該返回值會返回給函數的調用端。語法上,使用return來實現。遇到return,函數就會終止執行,同時將return后的值返回給調用端
當調用函數時,可以近似的認為是函數的返回值替換掉了函數的調用。
如果在函數內沒有顯式使用return返回值,則返回值為None。
def add(x,y):return x + yprint("不會執行") # 函數的返回值替換掉了函數的調用 result = add(1,1) print(result) # 函數總有一個返回值,如果沒有顯示調用return,默認為None def add1(x,y):sum = x + y result1 = add(1,1) print(result1)2.3 返回多個值
在函數中,當執行到return語句時,函數就會終止執行,返回給函數的調用端,這就是說,return之后的語句不會再執行。因此,函數體中不能通過執行多個return,返回多個值。可以通過將多個值放入元組(列表或字典)中,從而返回多個值。
# 返回多個值。可以通過將多個值放入元組(列表或字典)中,元組比較合適,因為元組不能修改 def operation(x,y):return (x + y,x - y,x * y,x / y) print(operation(2,2))3 參數的默認值
3.1 可選參數
如果一個參數含有默認值,則該參數就稱為一個可選參數,可有可無。
如果顯式傳遞了該函數,則該參數的值就是我們傳遞的值,如果沒有則是該參數的默認值
3.2 參數的默認值
(1)參數默認值的要求
如果形參含有默認值,則在函數定義時,含有默認值的形參一定要定義在不含默認值的形參之后。
這是因為,如果形參含有默認值,則意味著,該參數是一個可選參數,在函數調用時,可以選擇性的進行傳值。如果允許含有默認值的形參定義在不含默認值的形參前面,就會造成混淆。
# 函數的定義 def print_triangle(length,char="*"):for i in range(length+1):for j in range(i+1,length+1):print(char,end=" ")print() # 函數的調用 print_triangle(3) print_triangle(3,"@")(2)含有默認值的意義
①可以另函數的功能變得簡單;
②可以兼容之前的程序
(3)注意事項
形參默認值,其只是在聲明的一刻進行求值,并且僅求值一次。因此,當使用可變類型作為參數默認值時,可能會帶來意外的結果。
在Python中,函數也是對象,我們給形參增加的默認值,也是保存在函數中。我們可以通過函數對象的__defaults__屬性來獲取函數形參的默認值。__defaults__返回的是一個元組類型,包含函數定義時,形參所有的默認值。
# 注意:函數的形式參數的默認值是在函數定義的時候解析器計算,不是調用的時候計算,因此參數的默認值只計算1次 num = 6 def add(a,b=num):print(a+b) num = 10 add(6)# 當默認值是可變對象時,要特別注意,每次調用都會改變 def fun(a=[]):a.append(10)print(a) fun() fun()# 獲取函數的默認值,返回的是一個元組 print(fun.__defaults__)4 位置參數與關鍵字參數
位置參數與關鍵字參數是針對實際參數而言的。
在調用函數時,如果函數定義了形式參數,則我們需要在調用時,傳遞相應的實際參數(如果形參具有默認值可不傳遞),形式為按順序,一一對應的方式。即第一個實參傳遞給第一個形參,第二個實參傳遞給第二個形參……,這種對位傳遞的實際參數為位置參數。
除了位置參數之外,我們還可以使用關鍵字參數。所謂關鍵字參數,就是在調用函數時,顯式指定“參數名=參數值”的形式,即指定要給哪一個形參傳遞值。
在調用函數時,可以混合使用位置參數與關鍵字參數。
4.1 關鍵詞做參數
優點:(1)增加程序的可讀性
(2)可以不考慮傳遞的順序
(3)當多個形參存在默認值時,關鍵字參數能夠指定賦值給哪個形參。
注意:
(1)位置參數必須在關鍵字參數前面
(2)同一個參數不能傳遞多次
(3)關鍵字參數的順序可以顛倒,但是提供的參數名必須存在
def add(a,b,c):pass # 位置參數傳遞值 add(1,2,3) # 關鍵字參數傳遞值 add(a=1,b=2,c=3) # 同一個參數只能傳遞一次,否則出錯 # add(1,2,c=3,b=5) 報錯 # 位置參數必須位于關鍵字參數前 # add(b=2,2,3) 報錯強制使用關鍵字參數:
了能夠增強程序的可讀性,同時實現編程風格的一致性,函數的定義者可以使用“*”語法,來限制“*”后面的所有參數必須都是以關鍵字參數的形式傳遞。強制使用關鍵字往往針對的是具有默認值的參數,但從語法的角度講,對于不含默認值的參數,也是可以的。
# 強制使用關鍵字參數 ,使用* def add(a,*,b=5):pass # add(1,2) TypeError: add() takes 1 positional argument but 2 were given # 正確寫法為 add(1,b=2)5 可變參數
5.1 兩種可變參數
(1)兩種可變參數
在定義函數時,我們可以定義可變參數(形式參數),所謂可變參數,即可以接收不定數量的實際參數。可變參數有兩種形式:①位置可變參數(*);②關鍵字可變參數(**)
(2)參數打包
函數調用時,位置可變參數(形參)可以用來接收所有未匹配的位置參數(實參),而關鍵字可變參數(形參)可以用來接收所有未匹配的關鍵字參數(實參)。在函數調用過程中,所有未匹配的位置參數會打包成一個元組,而所有未匹配的關鍵字參數會打包成一個字典。
# *v位置可變參數,**kv關鍵字可變參數 def fun(*v, **kv):print(type(v))print(v)print(type(kv))print(kv) fun(1,2,3,a=11,b=22)注意:
①可變參數只能接收未匹配的(剩下的)實際參數,如果實際參數已經匹配某形式參數,則不會由可變參數接收。
②在參數位置上,關鍵字可變參數必須位于函數參數列表的最后。
③在位置可變參數后面定義的參數,將自動成為關鍵字參數。
④位置可變參數與關鍵字可變參數各自最多只能定義一個。
(3)位置可變參數
def sum(*value):
(4)關鍵字可變參數
在接收一些可選的選項,設置時,對于選項較多時,如果將每個選項都用一個關鍵字參數來接收時,會顯得函數定義特別龐大,此時,就可以使用一個關鍵字可變參數來實現。
# 位置可變參數,可以用來接收任意數量的位置參數,在參數前面加* # 在函數調用過程中,會執行打包操作,位置可變參數是一個元組類型 def sum(*a):print(type(a))sum = 0for i in a:sum += ireturn sum result = sum(1,2,3) print(result)# 關鍵字可變參數,可以用來接收任意數量的關鍵字參數,在參數前面加上** # 在函數調用過程中,會執行打包操作,關鍵字參數打包成字典類型 def sum(**kv):print(type(kv))print(kv) sum(a=1,b="abc",c=[1,2,3])# 萬能參數列表,可變關鍵字參數必須位于參數列表最后 def omnipotent(*a,**kv):pass5.2 打包與拆包
拆包:將序列類型拆分為多個位置參數,或者將字典類型拆分成多個關鍵字參數。
其實,在進行平時賦值時,也會隱式發生元組的打包與拆包操作。
# 拆包 def sum(a,b,c):print(a,b,c) # 序列拆包 param = (1,2,3) sum(*param)# 字典拆包,分解為若干關鍵字參數的形式 param2 = {"a":"aaa","b":"bbb","c":"ccc"} sum(**param2)6 參數傳遞
同過參數傳遞,我們可以使形式參數與實參綁定相同的對象,通過形參可以改變實參所綁定的數據對象。
注意:不能通過改變形參所綁定的對象,進而影響實參。實參所綁定的對象不會進行更改,即在函數調用前,實參綁定到哪個對象,在函數調用之后,實參依然會綁定原來的對象。
def fun(a):a.append(100) li = [1,2,3] fun(li) print(li)def fun(a):a = [] li = [1,2,3] fun(li) print(li)7 命名空間與作用域
7.1 命名空間
命名空間:可以認為是保存命名的一個容器,當定義變量,函數,類等結構時,相關的名稱就保存在相應的命名空間中。根據名稱在當前模塊(文件)中定義的位置,根據命名可以將名稱分為兩類:局部命名空間(定義在函數內的名稱(函數的參數,變量,嵌套函數等))和全局命名空間(定義在函數、類外的名稱(處于當前模塊的頂級)。)
命名空間可以有多個,不同的命名空間可以保存相同的名稱,彼此之間不會受到干擾
命名空間可以分為如下幾類:
①內建命名空間:保存內建的名稱。例如,print,sum等內建函數。內建命名空間在Python解釋器啟動時就會創建,直到程序運行結束。
②全局命名空間:保存當前模塊(文件)中出現在頂層位置的名稱。例如:全局變量,頂層函數等。全局命名空間與模塊相關,在讀取模塊定義時創建,直到程序運行結束。
③局部命名空間:保存局部名稱。例如,局部變量,函數參數,嵌套函數等。局部命名空間在函數執行時創建,在函數執行結束后銷毀。
所以我們定義的函數,變量等其名稱屬于哪個命名空間,由其定義的位置決定。對于變量,定義的位置就是變量第一次賦值的位置。對于函數,定義的位置就是def出現的時刻。
# 全局變量 x = 6 # 全局函數 def overall():# 局部變量y = 1# python函數可以嵌套# 局部函數def inner():pass7.2 作用域
作用域,就是命名空間的有效區域。在作用域內,我們可以直接訪問命名空間中的名稱。
①內建命名空間,作用域為所有模塊(文件)。在任何模塊(文件)中均可直接訪問內建命名空間內的名稱。
②全局命名空間,作用域為當前模塊(文件)。在當前模塊(文件)中可直接訪問全局命名空間內的名稱,在其他模塊(文件)中,需要先導入該模塊,然后才能訪問(模塊導入的內容后面介紹)。
③局部命名空間,作用域為當前函數,在函數內可訪問局部命名空間內的名稱,在函數外則無法訪問。
7.3 LEGB原則
在訪問名稱時,會根據LEGB的順序來搜索名稱,即搜索順序為L?->?E?->?G?->?B。描述如下:
①L(Local):本地作用域,即包含所訪問名稱的最小作用域(當前函數的局部命名空間)。
②E(Enclosing)外圍作用域,當函數嵌套時會存在這種情況,即訪問外層函數的作用域,如果沒有找到,并且外層函數依然還有外層函數(函數多層嵌套),則會繼續搜索更外層的函數,直到頂層函數位置(對應外層函數的局部命名空間)
③G(Global)全局作用域,當前模塊的作用域(全局命名空間)。
④B(Build-in)內建作用域,所有內建的名稱(內建命名空間)。
在讀取名稱值時:如果該變量在當前作用域沒有發現,則會按照LEGB的方式依次進行查找,以先找到的為準。如果到最后也沒有找到,則會產生NameError錯誤。
為名稱賦值時:會在程序執行處的最小作用域內尋找變量,如果變量存在,則修改該變量的綁定,如果該變量不存在,則不會再按照LEGB的順序查找,而是在當前作用域的命名空間中,新建名稱,并綁定所賦予的值。這也就是說,我們無法修改其他命名空間中名稱的綁定。
注意:命名空間有什么在函數解析的時候已經確定下來了,盡管命名空間是在調用的時候才創建但是該有什么沒有什么在函數解析的時候已經能夠決定好的,不會等到一步步執行的時候才確定下來
# 全局變量 x = 6 # 全局函數 def overall():x = 8print(x) overall() print(x)def overall2():# 命名空間中存在哪些名稱不是在執行的時候一點一點創建出來的,是在解析編譯的時候已經能夠決定有哪些。在解析的時候已經發現有x了,那就認為會在當前的這個命名空間中去創建這個x,所以在print的時候就認為是這個局部的命名空間,而不是全局的命名空間x。因為print訪問x的時候發現還沒有被賦值(沒有綁定任何有效的對象),所以報錯# print(x) 報錯:UnboundLocalError: local variable 'x' referenced before assignmentx = 8 overall2()def overall3():# x = x + 1 報錯:UnboundLocalError: local variable 'x' referenced before assignment 原因如上訪問x的時候x還沒有被賦值pass overall3()刪除命名空間是:當刪除名稱時,其表現行為與為名稱賦值是一致的。即只能刪除當前作用域的名稱,而不能刪除外層作用域的名稱。
nonlocal與global:當期望對外部作用域的名稱進行修改時,可以使用nonlocal或者global。
注意:nonlocal指定的名稱不存在,會產生錯誤,而global指定的名稱不存在則不會產生錯誤,但是該名稱也不會自動創建,如果訪問該名稱,依然會產生錯誤。
# 內建命名空間的優先級最低,所以我們自己定義的名稱一定會覆蓋命名空間的名稱 id = 6 print(id) # 在當前命名空間下無法修改或刪除其他命名空間的名稱,如果希望修改其他命名空間的 # 名稱,可以借助global或nonlocal del id print(id(8)) # 全局命名空間 def overall():# 進行聲明,表示我們要操作的是全局命名空間,而不是當前命名空間global aa = 66 overall() print(a)def overall2():b = 88def inner():# 進行聲明,表示我們要操作的是外圍命名空間中的名稱,而不是當前局部命名空間中的名稱nonlocal bb = 99inner()print(b) overall2() # 注意:global指定的名稱如果不存在,不會產生錯誤,而nonlocal指定的名稱不存在會產生錯誤 def fun():global no_existdef inner():# nonlocal no_exist 指定的命名空間不存在報錯passinner() fun()8 Lamabda表達式
對于函數而言,函數名也是一個名稱,該名稱綁定函數對象。因此,我們也可以像變量那樣,對函數名進行相應操作,如:①將函數名賦值給一個變量②將函數名作為實際參數傳遞給其他函數③函數名可以作為另外一個函數的返回值
可以使用Lambda表達式來創建函數,Lambda表達式的語法為:
? ? lambda [參數]: 表達式
參數是可選的,表達式的結果值,就是函數的返回值。lambda表達式的函數體僅能存在一條語句,故lambda表達式適用于功能簡單的函數。也因為lambda表達式創建的函數沒有名字,因此也稱為匿名函數。
# 變量與函數都是一個名稱,函數名可以像變量名那樣操作 def print_hello():print("hello") print_hello() # 函數名可以進行賦值,賦值給一個變量 a = print_hello # 通過a調用函數 a() def fun(b):return abs(b) li = [-5,-2,6] # 將函數名作為實際參數傳遞給其他函數 li.sort(key=fun) print(li) # 函數名可以作為另外一個函數的返回值 def outer():c= 6def inner():passreturn inner d = outer()# lambda表達式用來定義匿名函數,lambda [參數]:返回值表達式 li.sort(key=lambda x : abs(x)) print(li) def lam():return lambda x : x + 29 遞歸
9.1 什么是遞歸
遞歸,就是一個函數直接或者間接調用自身的過程。
函數如果無條件的調用自身,這是沒有意義的,最終也會導致超過最大遞歸深度而產生錯誤。因此,我們必須能夠找出,讓遞歸終止的條件。
遞歸,可以分為遞推與回歸。遞推:就是解決問題,逐層的進行推理,得出遞歸的終止點。回歸:則是利用剛才遞推取得終止點的條件,再一層層返回,最終取得答案的過程。
9.2 循環與遞歸
循環與遞歸具有一定的相似度,二者往往可以實現相同的效果。理論上,能夠通過循環解決的問題,使用遞歸也能夠實現。
從思想的角度看,循環是重復性的執行一件相同或者相似的事情,進而得出結果。而遞歸則是將問題分解為更小的問題,直到該問題能夠解決,然后再由最小問題的答案,逐步回歸,從而解決更大的問題,最終解決整個問題的過程。
遞歸的默認深度為1000。
# 循環求和 def loop(a):sum = 0for i in range(1,a+1):sum += ireturn sum print(loop(10)) # 遞歸求和 def recur(b):if b == 1:return 1else:return b + recur(b-1) print(recur(10))9.3 遞歸的深度
遞歸的默認深度為1000。遞歸的默認深度,應該將其理解為最大函數調用限制。
# 遞歸的深度 import sys # 返回遞歸的最大深度 print(sys.getrecursionlimit()) # 設置遞歸的最大深度 sys.setrecursionlimit(3) def first():two() def two():third() def third():four() def four():pass first()10 高階函數
如果一個函數的接收另外一個函數作為參數,或者該函數將另外一個函數作為返回值,則這樣的函數稱為高階函數。
常用的高階函數有:
(1)map(func, *iterables)
def sum(a):return a + a # map的第一個參數為一個函數,第二個參數為一個可迭代的對象 # 對可迭代對象中的每一個元素,調用一次函數(即第一個參數),將該元素傳入獲得返回值,將所有返回值作為一個可迭代對象返回 m = map(sum,[6,8,10]) print(m) for i in m:print(i,end=" ") print() # 上面的函數可以用lambda m1 = map(lambda a : a+a, [6,8,10]) for i in m1:print(i,end=" ") print() # map可用列表推導式代替,所以使用頻率較低 li = [6,8,10] print([i + i for i in li])(2)filter(function or None, iterable)
# filter,第一個參數為一個函數,該函數具有一個參數,第二個參數為可迭代對象 # 對可迭代對象中的每一個元素,調用一次函數,將元素傳入,獲得返回值,將所有返回值為True的元素保留,返回一個可迭代對象 f = filter(lambda a : a > 3,[1,2,3,4,5,6]) for i in f:print(i,end=" ") print() # filter也可用列表推導式代替,所以使用頻率較低 li=[1,2,3,4,5,6] print([i for i in li if i>3])(3)reduce(function, sequence[, initial]) -> value
從Python3.0起,reduce不在是內置函數(Python2是內置函數),而是將其遷移到了functools模塊中。
# reduce,第一個參數為一個函數,具有兩個參數,第二個參數為可迭代對象,第三個參數為初始值(可選) import functools li = list(range(1,6)) print(functools.reduce(lambda x,y:x+y,li))11 函數描述
11.1 函數說明文檔
說明文檔,就相當于是函數的使用說明書,在文檔中可以用來說明函數的功能,參數,返回值類型以及相關注意事項,使用示例等。要為函數編寫說明文檔,可以在函數體的上方,使用字符串進行標記。按照慣例,作為說明文檔的字符串使用三個雙引號界定,并且第一行為函數的一個簡要說明,接下來是一個空行,然后是函數的具體說明。
def fun():
? ? ?"""這里是函數的簡要說明。
?
? ? ?這是是函數的具體功能說明。(注意存在一個空行)
? ? ? """
? ? ?# 函數語句
注釋內容,則解釋器會忽略,但是,作為函數文檔,我們可以通過函數的__doc__屬性獲取這些說明,方便我們進行查看。同時,我們使用help函數來獲取幫助時,返回的就是函數的說明文檔信息。
11.2 函數注解
在Python中,變量是沒有類型的,對于函數來說,無法像C或Java語言那樣,只要觀看函數的定義,就可以輕松的確定函數的參數與返回值類型。為了彌補這種不足,使代碼更具有可讀性與易用性,我們可以使用函數注解來進行標注,顯式指定函數參數或返回值的類型。
def add(a: int, b: int=3) ->int:
參數的說明是在參數后面加上一個:然后給出參數的類型。根據慣例,:與其后的類型使用一個空格分隔。返回值說明是在參數列表的)后,使用->指出。
可以使用函數對象的__annotations__屬性獲取函數的注解信息。該屬性是一個字典類型。
?
?
?
?
?
?
?
總結
以上是生活随笔為你收集整理的Python基础(五)--函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: RDD持久化、广播、累加器
- 下一篇: 安装Python第三方库的常用方法和注意