04 python之函數詳解
- 2019 年 10 月 3 日
- 筆記
一、函數初識
函數的產生:函數就是封裝一個功能的程式碼片段。
li = ['spring', 'summer', 'autumn', 'winter'] def function(): count = 0 for j in li: count += 1 print(count) function() # 4
- def 關鍵字,定義一個函數
- function 函數名的書寫規則與變數一樣。
- 括弧是用來傳參的。
- 函數體,就是函數裡面的邏輯程式碼
程式碼從上至下執行,執行到def function() 時, 將function這個變數名載入到臨時記憶體中,但它不執行。
函數的執行:函數名 + ()
使用__name__方法獲取函數名 ,使用__doc___方法獲取函數的解釋
def func1(): """ 此函數是完成登陸的功能,參數分別是...作用。 return: 返回值是登陸成功與否(True,False) """ print(666) func1() print(func1.__name__) #獲取函數名 print(func1.__doc__) #獲取函數名注釋說明
執行輸出: 666 func1 此函數是完成登陸的功能,參數分別是...作用。 return: 返回值是登陸成功與否(True,False)
這個有什麼用呢?比如日誌功能,需要列印出誰在什麼時間,調用了什麼函數,函數是幹啥的,花費了多次時間,這個時候,就需要獲取函數的有用資訊了
1. 函數返回值
寫函數,不要在函數中寫print(), 函數是以功能為導向的,除非測試的時候,才可以寫print()
- 在函數中,遇到return結束函數
def fun(): print(111) return print(444) fun()
執行輸出:111
- 將值返回給函數的調用者
def fun(): a = 134 return a print(fun())
執行輸出:123
1)無 return
def fun(): pass print(fun())
執行輸出:None
2)return 1個值。該值是什麼,就直接返回給函數的調用者,函數名()
def fun(): return [1,2,3] print(fun())
執行輸出:[1, 2, 3]
3)return 多個值 將多個值放到一個元組里,返回給函數的調用者。
def fun(): return 1,2,[33,44],'abc' print(fun())
執行輸出: (1, 2, [33, 44], 'abc')
2. 函數的傳參
(1)實參:在函數執行者裡面的參數叫實參
①位置參數:按順序,一一對應
def func(a,b,c): print(a) print(b) print(c) func('fdsafdas',3,4)
執行輸出: fdsafdas 3 4
如果少一個參數呢?
def func(a,b,c): print(a) print(b) print(c) func(3,4)
執行報錯:TypeError: func() missing 1 required positional argument: 'c'
必須是一一對應的。
def compare(x,y): ret = x if x > y else y #三元運算,針對簡單的if else才能使用 return ret print(compare(123,122334)) # 122334
②關鍵字參數:可以不按順序,但是必須一一對應
def compare(x,y): ret = x if x > y else y return ret print(compare(y=13,x=1))
執行結果:13
③混合參數:關鍵字參數一定要在位置參數後面
def func1(a,b,c,d,e): print(a) print(b) print(c) print(d) print(e) func1(1,4,d=2,c=3,e=5)
執行輸出: 1 4 3 2 5
(2) 形參:
①位置參數:按順序和實參一一對應,位置參數必須傳值
def func(a,b,c): print(a) print(b) print(c) func('fdsafdas',3,4)
執行輸出: fdsafdas 3 4
②默認參數:傳參則覆蓋,不傳則默認,默認參數永遠在位置參數後面
例1.
def func(a,b=666): print(a,b) func(1,2)
執行輸出:1 2
例2.
def func(a,b=666): print(a,b) func(1) 執行輸出:1 666
舉一個場景:班主任錄入員工資訊表,有2個問題:第一,男生居多;第二,完成函數功能 *****
def Infor(username,sex='男'): with open('name_list',encoding='utf-8',mode='a') as f1: f1.write('{}t{}n'.format(username,sex)) while True: username = input('請輸入姓名(男生以1開頭):').strip() if '1' in username: username = username[1:] #去除1 Infor(username) else: Infor(username,'女')
③動態參數:當函數的形參數量不一定時,可以使用動態參數。用*args和**kwargs接收,args是元組類型,接收除鍵值對以外的參數(接收位置參數),kwargs是字典類型,接收鍵值對(關鍵字參數),並保存在字典中。
def func(*args,**kwargs): print(args,type(args)) print(kwargs,type(kwargs)) func(1,2,3,4,'alex',name = 'alex')
輸出結果是: (1, 2, 3, 4, 'alex') <class 'tuple'> {'name': 'alex'} <class 'dict'>
“ * “的魔性作用
(1)在函數定義時:*位置參數和**關鍵字參數代表聚合
將所有實參的位置參數聚合到一個元組中,並將這個元組賦值給args。在關鍵參數前加“ ** ”代表將實參的關鍵字參數聚合到一個字典中,並將這個字典賦值給kwargs。
將2個列表的所有元素賦值給args
def func(*args): print(args) l1 = [1,2,30] l2 = [1,2,33,21,45,66] func(*l1) func(*l1,*l2)
執行輸出: (1, 2, 30) (1, 2, 30, 1, 2, 33, 21, 45, 66)
傳兩個字典給**kwargs
def func(**kwargs): print(kwargs) dic1 = {'name':'jack','age':22} dic2 = {'name1':'rose','age1':21} func(**dic1,**dic2)
執行輸出: {'name': 'jack', 'age': 22, 'name1': 'rose', 'age1': 21}
def func(*args,**kwargs): print(args) print(kwargs) func(*[1,2,3], *[4,5,6], **{'name':'alex'}, **{'age':18}) #相當於func([1,2,3,4,5,6], {'name':'alex','age':18})
(2)在函數的調用執行時,打散
*可迭代對象,代表打散(list,tuple,str,dict(鍵))將元素一一添加到args。
**字典,代表打散,將所有鍵值對放到一個kwargs字典里。
def func(*args,**kwargs): print(args,kwargs) dic1 = {'name':'jack','age':22} dic2 = {'name1':'rose','age1':21} func(*[1,2,3,4],*'asdk',**dic1,**dic2)
執行輸出:(1, 2, 3, 4, 'a', 's', 'd', 'k') {'age1': 21, 'name': 'jack', 'age': 22, 'name1': 'rose'}
形參的順序:位置參數 —-> *args —–>關鍵字參數——–>默認參數 ——->**kwargs
*args參數,可以不傳,默認為空(),**kwargs 動態傳參,他將所有的關鍵字參數(未定義的)放到一個字典中
def func(a,b,c,d,*args,e='男',**kwargs): print(a,b,c,d,args,e,kwargs) func(1,2,3,4,5,6,7,v=3,m=7,h=9,e='女')
執行輸出:1 2 3 4 (5, 6, 7) 女 {'v': 3, 'h': 9, 'm': 7}
def func(a,b,c,**kwargs): print(kwargs) func(1,2,r=4,b1=5,c1=6,c=7) 執行輸出:{'r': 4, 'c1': 6, 'b1': 5}
執行沒有報錯,是因為函數接收參數後,它會從左邊到右找,最後找到了c,c=7參數,在a,b,c裡面已經定義好了,所以在輸出的字典中,並未出現。因為kwargs返回的是未定義的關鍵字參數。
如果函數含有多個未知參數,一般使用如下格式:
def func1(*args,**kwargs): pass func1()
二、命名空間和作用域
當執行函數的時候,他會在記憶體中開闢一個臨時名稱空間,存放函數體內的所有變數與值的關係,隨著函數的執行完畢,臨時空間自動關閉。
函數裡面的變數,在函數外面能直接引用么?不能
def func1(): m = 1 print(m) print(m) # NameError: name 'm' is not defined
上面為什麼會報錯呢?現在我們來分析一下python內部的原理是怎麼樣:
我們首先回憶一下Python程式碼運行的時候遇到函數是怎麼做的,從Python解釋器開始執行之後,就在記憶體中開闢里一個空間,每當遇到一個變數的時候,就把變數名和值之間對應的關係記錄下來,但是當遇到函數定義的時候,解釋器只是象徵性的將函數名讀入記憶體,表示知道這個函數存在了,至於函數內部的變數和邏輯,解釋器根本不關心。等執行到函數調用的時候,Python解釋器會再開闢一塊記憶體來儲存這個函數裡面的內容,這個時候,才關注函數裡面有哪些變數,而函數中的變數會儲存在新開闢出來的記憶體中,函數中的變數只能在函數內部使用,並且會隨著函數執行完畢,這塊記憶體中的所有內容也會被清空。
1. 命名空間和作用域
命名空間:存放”名字與值關係的空間“
①全局命名空間:程式碼在運行時,創建的存儲”變數名與值的關係“的記憶體空間
②局部命名空間:在函數調用時臨時開闢出來的空間,會隨著函數的執行完畢而被清空
③內置命名空間:存放了python解釋器為我們提供的名字:input,print,str,list,tuple…它們都是我們熟悉 的,拿過來就可以用的方法。
作用域:就是作用範圍
①全局作用域:全局命名空間、內置命名空間。在整個文件的任意位置都能被引用、全局有效
②局部作用域:局部命名空間,只能在局部範圍內生效
載入順序:
內置命名空間(程式運行前載入)—–> 全局命名空間(程式運行中從上至下載入) —–> 局部命名空間(程式運行中:調用時才載入)
取值順序:
在局部調用:局部命名空間->全局命名空間->內置命名空間
在全局調用:全局命名空間->內置命名空間
綜上所述,在找尋變數時,從小範圍,一層一層到大範圍去找尋。取值順序:就近原則
局部變數舉例
name = 'summer' def func1(): name = 'spring' print(name) func1()
執行輸出:spring
取值是從內到外
name = 'summer' def func1(): print(name) func1()
執行輸出:summer
程式碼從上至下依次執行, 調用函數:函數裡面從上至下依次執行。
print(111) def func1(): print(333) func2() print(666) def func2(): print(444) def func3(): print(555) func2() func1() print(222)
執行輸出: 111 333 444 666 222
def f1(): def f2(): def f3(): print("in f3") print("in f2") f3() print("in f1") f2() f1()
執行輸出: in f1 in f2 in f3
2. globals和locals方法
print(globals()) #全局名稱空間所有變數,字典 print(locals()) #局部名稱空間所有變數,字典 (當前)
globals()和locals()一般很少用,在函數邏輯比較複雜的情況下,可能會用到。
li = ['spring', 'summer', 'autumn', 'winter'] def func(): a = 1 b = 2 print('func', globals()) print('func', locals()) def func1(): c = 3 d = 4 print('func1', globals()) print('func1', locals()) func1() func()
輸出結果
func {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x011CC410>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'C:/Users/Administrator/houseinfo/test.py', '__cached__': None, 'li': ['spring', 'summer', 'autumn', 'winter'], 'func': <function func at 0x03542E40>} func {'b': 2, 'a': 1} func1 {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x011CC410>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'C:/Users/Administrator/houseinfo/test.py', '__cached__': None, 'li': ['spring', 'summer', 'autumn', 'winter'], 'func': <function func at 0x03542E40>} func1 {'d': 4, 'c': 3}
(1)global:
①在局部命名空間聲明全局變數
def func2(): global name name = 'summer' func2() print(name)
執行結果:summer
②在局部命名空間對全局變數進行修改(限於字元串,數字)。
count = 1 def func1(): global count count = count + 1 print(count) func1() print(count)
執行結果:
2
2
因為全局變數count被函數體的global count 覆蓋了
(2)nonlocal
①子函數對父函數的變數進行修改,此變數不能是全局變數
a = 4 def func1(): nonlocal a a = 5 #修改全局變數 #print(name) func1() print(a)
執行輸出:SyntaxError: no binding for nonlocal 'a' found
②在局部作用域中,對父級作用域的變數進行引用和修改,並且引用的哪層,從那層及以下此變數全部發生改變。
例1
def func1(): b = 6 def func2(): b = 666 print(b) func2() print(b) #父級不受影響 func1()
執行輸出: 666 6
例2
def func1(): b = 6 def func2(): nonlocal b #表示可以影響父級,也就是func1() b = 666 #重新賦值 print(b) func2() print(b) #這個時候,影響了b的值,輸出666 func1()
執行輸出: 666 666
例3******
def aa(): #不受ccl影響 b = 42 def bb(): b = 10 #影響子級函數,b都是10 print(b) def cc(): nonlocal b #只能影響父級,也就是bb() b = b + 20 #b=10+20 也就是30 print(b) cc() print(b) bb() print(b) aa()
執行輸出: 10 30 30 42
注意
a = 5 def func1(): a += 1 print(a) func1()
執行報錯。這裡函數對全局變數做了改變,是不允許操作的。函數內部可以引用全局變數,不能修改。如果要修改,必須要global一下
a = 5 def func1(): global a a += 1 print(a) func1() #輸出6