爆肝六萬字整理的Python基礎,快速入門python的首選(下)(Python干貨)
10 函數
函數是組織好的,可重復使用的,用來實現單一,或相關聯功能的代碼段。
函數能提高應用的模塊性,和代碼的重復利用率。你已經知道Python提供了許多內建函數,比如print()。但你也可以自己創建函數,這被叫做用戶自定義函數。
10.1 定義函數
語法:
def 函數名(參數列表): 函數體
例:
# 定義一個函數,能夠完成打印信息的功能 def printInfo(): print ('------------------------------------') print (' 人生苦短,我用Python') print ('------------------------------------')
定義函數的規則:
函數代碼塊以 def 關鍵詞開頭,后接函數標識符名稱和圓括號 ()。
任何傳入參數和自變量必須放在圓括號中間,圓括號之間可以用于定義參數。
函數的第一行語句可以選擇性地使用文檔字符串—用于存放函數說明。
函數內容以冒號 : 起始,并且縮進。
return [表達式] 結束函數,選擇性地返回一個值給調用方,不帶表達式的 return 相當于返回 None。
10.2 調用函數
定義了函數之后,就相當于有了一個具有某些功能的代碼,想要讓這些代碼能夠執行,需要調用它
調用函數很簡單的,通過 函數名() 即可完成調用,例:
# 定義完函數后,函數是不會自動執行的,需要調用它才可以 printInfo()
10.3 函數的注釋
在函數定義下面使用'''進行注釋,使用help函數可以查看函數的注釋。
def test(a1, a2): ''' 用來完成對2個數求和" :param a1:第一個參數 :param a2:第二個參數 :return:無 ''' print("%d" % (a1 + a2)) print(help(test))
運行結果:
test(a1, a2) 用來完成對2個數求和" :param a1:第一個參數 :param a2:第二個參數 :return:無 None
10.4 函數的參數
10.4.1 參數的定義
參數分形參、實參
形參:函數定義時括號內的參數
實參:函數調用時括號內的參數
形參相當于變量,實參相當于變量的值。
def add2num(a, b): c = a + b print(c) add2num(11, 22) # 調用帶有參數的函數時,需要在小括號中,傳遞數據
a,b為形參;11,12為實參
形參:
只在被調用時,才分配內存單元。調用結束,立刻釋放所分配的內存。
只在函數內部有效。
實參:
可以是:常量、變量、表達式、函數。
進行函數調用時,實參必須是確定的值。
10.4.2 參數的分類
Python的函數除了正常定義的必選參數外,還可以使用默認參數、可變參數和關鍵字參數,使得函數定義出來的接口,不但能處理復雜的參數,還可以簡化調用者的代碼。
1)位置參數
位置形參:函數定義時,從左往右寫的參數,比如上面的 a, b
位置實參:函數調用時,從左往右寫的參數, 比如上面的 11,12
位置形參定義多少個,調用時位置實參必須寫上多少個,多一個少一個都不行。
2)關鍵參數:
正常情況下,給函數傳參數,要按順序。如果不想按順序,就用關鍵參數。
指定參數名的參數,就叫做關鍵參數。
函數調用時:func(a=1, b=2), 這種指定了參數名的參數,就是關鍵參數。
調用函數時,關鍵參數可以和位置參數一起用,但是關鍵參數必須在位置參數的后面。不然會報錯。
例:
def add2num(a, b): c = a + b print(c) #正確的調用方式 add2num(11, b=22) # 調用帶有參數的函數時,需要在小括號中,傳遞數據 add2num(a=11, b=22) add2num(b=11, a=22) # 錯誤的調用方式。 #add2num(22, a=22) #add2num(b=11,22)
3)默認參數
函數定義時,默認參數必須在位置形參的后面。
函數調用時,指定參數名的參數,叫關鍵參數。
而在函數定義時,給參數名指定值的時候,這個參數叫做默認參數。
關鍵參數,和默認參數兩個參數寫法一樣,區別在于:
關鍵參數是在函數調用時,指定實參的參數名,也可以說指定值的參數名。
默認參數是在函數定義時,指定參數名的值。
定義時,有默認參數的話,調用時,這個實參可以不寫。如果實參不寫的話,這個形參的參數值是他的默認值。
例:
def add2num(a, b=100): c = a + b print(c) add2num(11, b=22) # 調用帶有參數的函數時,需要在小括號中,傳遞數據 add2num(11)
運行結果:
33 111
4 ) 動態參數:*args **kwargs
*args
針對函數定義時的*:
def func(a, b, *args):
pass
*args會接收函數調用時,傳入的多余的位置實參。
*args 是一個元組
例子:
func(1, 2, 3, 4, 5, 6) 函數調用,因為函數定義時,*args前面有形參a, 形參b, *args就接收調用時多余的位置實參
a為1, b為2, *args 為: (3, 4, 5, 6),是一個元組。
針對函數調用時的 *:
func(1, 2, *[1, 2, 3, 4]) == func(1, 2, 1, 2, 3, 4)
函數調用時有*, 就應該立馬將*后面的列表,元組,字符串之類的迭代器,打散成位置參數來看。
注意,如果 *后面是一個字典的話,那么打散成的位置參數就是字典的key
*可以看做是for循環。
形參中 *args 只能接收多余的位置實參,成為一個元組。不能接收關鍵實參。
例:
def calc(*numbers): sum = 0 for n in numbers: sum = sum + n * n return sum nums = [1, 2, 3] # 調用方式1 print(calc(nums[0], nums[1], nums[2])) #調用方式2 print(calc(*nums))
運行結果:
14 14
**kwargs:
針對函數定義時,站在形參的角度看 **:
接收多余的關鍵實參,成為一個字典dict。
字典的key是關鍵實參的變量名,字典的value是關鍵實參的值。
將字典交給**后面的變量名,這里是kwargs
站在實參的角度看 ** :
d = {‘x’:1, ‘y’:2, ‘z’:333}
func(**d) # 等價于func(x=1,y=2,z=333)
函數調用時,后面可以接一個字典,然后會把字典打散成關鍵參數的形式,也就是key=value的形式。
例:
def person(name, age, **kw): print('name:', name, 'age:', age, 'other:', kw) #調用方式1 print(person('Michael', 30)) #調用方式2 print(person('Bob', 35, city='Beijing')) #調用方式3 print(person('Adam', 45, gender='M', job='Engineer'))
運行結果:
name: Michael age: 30 other: {} None name: Bob age: 35 other: {'city': 'Beijing'} None name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'} None
5)混合參數時,參數順序
函數定義時:
從左往右:位置形參 > *args > 默認參數 > **kwargs
位置形參 > 默認參數 > *args > **kwargs 也可以。
因為函數調用時給的實參滿足了位置形參、默認參數之后,會把多余的位置實參給args。這樣并不會報錯。
但是 **kwargs 必須在 *args后面
默認形參必須在位置形參后面
函數調用時:
從左到右:位置實參 > *args > 關鍵參數 > **kwargs
因為 * args 在函數調用時,會被打散成位置實參,而關鍵參數必須在位置實參的后面,否則會報錯。SyntaxError: positional argument follows keyword argument
*args 必須在 **kwargs后面, 否則會報語法錯誤:SyntaxError: iterable argument unpacking follows keyword argument unpacking
總結
Python的函數具有非常靈活的參數形態,既可以實現簡單的調用,又可以傳入非常復雜的參數。
默認參數一定要用不可變對象,如果是可變對象,程序運行時會有邏輯錯誤!
要注意定義可變參數和關鍵字參數的語法:
*args是可變參數,args接收的是一個tuple;
**kw是關鍵字參數,kw接收的是一個dict。
以及調用函數時如何傳入可變參數和關鍵字參數的語法:
可變參數既可以直接傳入:func(1, 2, 3),又可以先組裝list或tuple,再通過*args傳入:func(*(1, 2, 3));
關鍵字參數既可以直接傳入:func(a=1, b=2),又可以先組裝dict,再通過**kw傳入:func(**{'a': 1, 'b': 2})。
使用*args和**kw是Python的習慣寫法,當然也可以用其他參數名,但最好使用習慣用法。
命名的關鍵字參數是為了限制調用者可以傳入的參數名,同時可以提供默認值。
定義命名的關鍵字參數在沒有可變參數的情況下不要忘了寫分隔符*,否則定義的將是位置參數。
10.4.3 參數的傳遞
Python參數傳遞采用的肯定是“傳對象引用”的方式。這種方式相當于傳值和傳引用的一種綜合。如果函數收到的是一個可變對象(比如字典或者列表)的引用,就能修改對象的原始值--相當于通過“傳引用”來傳遞對象。如果函數收到的是一個不可變對象(比如數字、字符或者元組)的引用,就不能直接修改原始對象--相當于通過“傳值’來傳遞對象。
例:
attrList = [0, 1, 2, 3, 4, 5] print("改變前————————————————————————————————") print(attrList) def func(i, attrList): attrList[i] = attrList[i] * 10 print("改變后————————————————————————————————") for i in range(3): func(i, attrList) print(attrList)
運行結果:
改變前———————————————————————————————— [0, 1, 2, 3, 4, 5] 改變后———————————————————————————————— [0, 10, 20, 3, 4, 5]
可以看到,List發生了改變,如果傳入不可變的元素呢?
a = 10 print("改變前————————————————————————————————") print(a) def func(c): print(id(c)) c += 2 print(id(c)) print("func函數的c值:", c) print(id(a)) func(a) print("改變后————————————————————————————————") print(a) print(id(a))
運行結果:
改變前———————————————————————————————— 10 140724141828160 140724141828160 140724141828224 func函數的c值: 12 改變后———————————————————————————————— 10 140724141828160
將a變量作為參數傳遞給了func函數,傳遞了a的一個引用,把a的地址傳遞過去了,所以在函數內獲取的變量c的地址跟變量a的地址是一樣的,但是在函數內,對c進行賦值運算,c的值從10變成了12,實際上10和12所占的內存空間都還是存在的,賦值運算后,c指向12所在的內存。而a仍然指向10所在的內存,所以后面打印a,其值還是10.
10.5 函數的返回值
想要在函數中把結果返回給調用者,需要在函數中使用return,例:
def add2num(a, b): c = a + b return c print(add2num(1, 2))
可以返回多個返回值,例:
def divid(a, b): shang = a // b yushu = a % b return shang, yushu sh, yu = divid(5, 2)
10.6 局部變量與全局變量
局部變量就是定義在一個函數體內部的變量
全局變量是定義在外面的變量
例:
a = 1 def f(): b = 2
其中a就是全局變量,而b是局部變量。局部變量只在函數體內部有效,出了函數體,外面是訪問不到的,而全局變量則對下面的代碼都有效。
全局變量可以直接在函數體內容部使用的,你可以直接訪問,但是注意的是,如果對于不可變類型的數據,如果在函數里面進行了賦值操作,則對外面的全局變量不產生影響,因為相當于新建了一個局部變量,只是名字和全局一樣,而對于可變類型,如果使用賦值語句,同樣對外部不產生影響,但是使用方法的話就會對外部產生影響。
g_b = 3 g_l1 = [1, 2] g_l2 = [1, 2, 3] def t1(): g_b = 2 g_l1 = [] g_l2.append(7) t1() print(g_b, g_l1, g_l2)
運行結果:3 [1, 2] [1, 2, 3, 7]
global關鍵字
上面說到,如果使用的是賦值語句,在函數內部相當于新建了一個變量,并且重新給了指向,但是有時候我們想把這個變量就是外部的那個全局變量,在賦值操作的時候,就是對全局變量給了重新的指向,這個時候可以通過global關鍵字表示我在函數里面的這個變量是使用的全局那個。使用方法如下:
g_b = 3 def t1(): global g_b g_b = 2 t1() print(g_b)
運行結果:2
這個時候你會發現全局變量g_b也重新指向了,這是因為global g_b表示指定了函數中的g_b就是外面的那個。
10.7 遞歸函數
如果一個函數在內部調用自身本身,這個函數就是遞歸函數。例:
def fact(n): if n == 1: return 1 return n * fact(n - 1) print(fact(5))
運行結果:120
可以根據函數定義看到計算過程如下:
===> fact(5) ===> 5 * fact(4) ===> 5 * (4 * fact(3)) ===> 5 * (4 * (3 * fact(2))) ===> 5 * (4 * (3 * (2 * fact(1)))) ===> 5 * (4 * (3 * (2 * 1))) ===> 5 * (4 * (3 * 2)) ===> 5 * (4 * 6) ===> 5 * 24 ===> 120
遞歸函數的優點是定義簡單,邏輯清晰。理論上,所有的遞歸函數都可以寫成循環的方式,但循環的邏輯不如遞歸清晰。
使用遞歸函數需要注意防止棧溢出。在計算機中,函數調用是通過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返回,棧就會減一層棧幀。由于棧的大小不是無限的,所以,遞歸調用的次數過多,會導致棧溢出。可以試試fact(1000)。
解決遞歸調用棧溢出的方法是通過尾遞歸優化,事實上尾遞歸和循環的效果是一樣的,所以,把循環看成是一種特殊的尾遞歸函數也是可以的。
尾遞歸是指,在函數返回的時候,調用自身本身,并且,return語句不能包含表達式。這樣,編譯器或者解釋器就可以把尾遞歸做優化,使遞歸本身無論調用多少次,都只占用一個棧幀,不會出現棧溢出的情況。
上面的fact(n)函數由于return n * fact(n - 1)引入了乘法表達式,所以就不是尾遞歸了。要改成尾遞歸方式,需要多一點代碼,主要是要把每一步的乘積傳入到遞歸函數中,例:
def fact(n): return fact_iter(n, 1) def fact_iter(num, product): if num == 1: return product return fact_iter(num - 1, num * product)
可以看到,return fact_iter(num - 1, num * product)僅返回遞歸函數本身,num - 1和num * product在函數調用前就會被計算,不影響函數調用。
fact(5)對應的fact_iter(5, 1)的調用如下:
===> fact_iter(5, 1) ===> fact_iter(4, 5) ===> fact_iter(3, 20) ===> fact_iter(2, 60) ===> fact_iter(1, 120) ===> 120
尾遞歸調用時,如果做了優化,棧不會增長,因此,無論多少次調用也不會導致棧溢出。
遺憾的是,大多數編程語言沒有針對尾遞歸做優化,Python解釋器也沒有做優化,所以,即使把上面的fact(n)函數改成尾遞歸方式,也會導致棧溢出。
小結
使用遞歸函數的優點是邏輯簡單清晰,缺點是過深的調用會導致棧溢出。
針對尾遞歸優化的語言可以通過尾遞歸防止棧溢出。尾遞歸事實上和循環是等價的,沒有循環語句的編程語言只能通過尾遞歸實現循環。
Python標準的解釋器沒有針對尾遞歸做優化,任何遞歸函數都存在棧溢出的問題。
10.8 匿名函數
10.8.1 定義
用lambda關鍵詞能創建小型匿名函數。這種函數得名于省略了用def聲明函數的標準步驟。
lambda函數的語法只包含一個語句,如下:
lambda [arg1 [,arg2,.....argn]]:expression
如下實例:
sum = lambda a1, a2: a1 * a2 # 調用sum函數 print("Value of total : ", sum(10, 20)) print("Value of total : ", sum(20, 20))
以上實例輸出結果:
Value of total : 200 Value of total : 400
Lambda函數能接收任何數量的參數但只能返回一個表達式的值
匿名函數不能直接調用print,因為lambda需要一個表達式
10.8.2 應用場合
例1:自己定義函數作為參數傳遞
def fun(a, b, opt): print("a =", a) print("b =", b) print("result =", opt(a, b)) fun(1, 2, lambda x, y: x + y)
運行結果:
a = 1 b = 2 result = 3
例2:作為內置函數的參數
想一想,下面的數據如何指定按age或name排序?
stus = [ {"name":"zhangsan", "age":18}, {"name":"lisi", "age":19}, {"name":"wangwu", "age":17} ]
按name排序:
stus.sort(key=lambda x: x['name']) print(stus)
運行結果:
[{'name': 'lisi', 'age': 19}, {'name': 'wangwu', 'age': 17}, {'name': 'zhangsan', 'age': 18}]
按age排序:
stus.sort(key=lambda x: x['age']) print(stus)
運行結果:
[{'name': 'wangwu', 'age': 17}, {'name': 'zhangsan', 'age': 18}, {'name': 'lisi', 'age': 19}]
11 文件操作
11.1 打開與關閉
11.1.1 打開文件
在python,使用open函數,可以打開一個已經存在的文件,或者創建一個新文件
open(文件名,訪問模式)
示例如下:
f = open('test.txt', 'w')
說明:
11.1.2 關閉文件
close( )
示例如下:
# 新建一個文件,文件名為:test.txt f = open('test.txt', 'w') # 關閉這個文件 f.close()
11.2 文件的讀寫
11.2.1 寫數據(write)
使用write()可以完成向文件寫入數據,例:
f = open('test.txt', 'w') f.write('hello 大家好, 我是AI浩') f.close()
運行現象:
注意:
如果文件不存在那么創建,如果存在那么就先清空,然后寫入數據
11.2.2 讀數據(read)
使用read(num)可以從文件中讀取數據,num表示要從文件中讀取的數據的長度(單位是字節),如果沒有傳入num,那么就表示讀取文件中所有的數據,例:
f = open('test.txt', 'r') content = f.read(5) print(content) print("-"*30) content = f.read() print(content) f.close()
運行結果:
hello ------------------------------ 大家好, 我是AI浩
注意:
如果open是打開一個文件,那么可以不用謝打開的模式,即只寫 open('test.txt')
如果使用讀了多次,那么后面讀取的數據是從上次讀完后的位置開始的
11.2.3 讀數據(readlines)
就像read沒有參數時一樣,readlines可以按照行的方式把整個文件中的內容進行一次性讀取,并且返回的是一個列表,其中每一行的數據為一個元素
#coding=utf-8 f = open('test.txt', 'r') content = f.readlines() print(type(content)) i=1 for temp in content: print("%d:%s"%(i, temp)) i+=1 f.close()
運行結果:
11.2.4 讀數據(readline)
#coding=utf-8 f = open('test.txt', 'r') content = f.readline() print("1:%s"%content) content = f.readline() print("2:%s"%content) f.close()
運行結果:
1:hello 大家好, 我是AI浩 2:asfsifhiudh
11.3 文件的常用操作
11.3.1 獲取當前讀寫的位置
在讀寫文件的過程中,如果想知道當前的位置,可以使用tell()來獲取,例:
# 打開一個已經存在的文件 f = open("test.txt", "r") str = f.read(3) print("讀取的數據是 : ", str) # 查找當前位置 position = f.tell() print("當前文件位置 : ", position) str = f.read(3) print("讀取的數據是 : ", str) # 查找當前位置 position = f.tell() print("當前文件位置 : ", position) f.close()
運行結果:
讀取的數據是 : hel 當前文件位置 : 3 讀取的數據是 : lo 當前文件位置 : 6
11.3.2 定位到某個位置
如果在讀寫文件的過程中,需要從另外一個位置進行操作的話,可以使用seek()
seek(offset, from)有2個參數
offset:偏移量
from:方向
0:表示文件開頭
1:表示當前位置
2:表示文件末尾
例1:把位置設置為:從文件開頭,偏移5個字節
# 打開一個已經存在的文件 f = open("test.txt", "r") str = f.read(30) print("讀取的數據是 : ", str) # 查找當前位置 position = f.tell() print("當前文件位置 : ", position) # 重新設置位置 f.seek(5, 0) # 查找當前位置 position = f.tell() print("當前文件位置 : ", position) f.close()
例2:把位置設置為:離文件末尾,3字節處
# 打開一個已經存在的文件 f = open("test.txt", "rb") print("讀取的數據是 : ", str) position = f.tell() print("當前文件位置 : ", position) # 重新設置位置 f.seek(-3, 2) # 讀取到的數據為:文件最后3個字節數據 str = f.read() print("讀取的數據是 : ", str) f.close()
運行結果:
讀取的數據是 :
11.3.3 文件重命名
os模塊中的rename()可以完成對文件的重命名操作
rename(需要修改的文件名, 新的文件名)
import os os.rename("test.txt", "test-1.txt")
11.3.4 刪除文件
os模塊中的remove()可以完成對文件的刪除操作
remove(待刪除的文件名)
import os os.remove("test.txt")
11.4 文件夾的相關操作
實際開發中,有時需要用程序的方式對文件夾進行一定的操作,比如創建、刪除等
就像對文件操作需要os模塊一樣,如果要操作文件夾,同樣需要os模塊
11.4.1 創建文件夾
import os os.mkdir("aa")
11.4.2 獲取當前目錄
import os os.getcwd()
11.4.3 改變默認目錄
import os os.chdir("../")
11.4.4 獲取目錄列表
import os os.listdir("./")
11.4.5 刪除文件夾
import os os.rmdir("張三")
11.4.6 檢測文件夾是否存在
import os if not os.path.exists(path): os.makedirs(path)
11.4.7 創建多級文件夾
import os os.makedirs(opDir)
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。