1.1 函數的參數python
*的魔性用法函數
函數形參最終順序ui
1.2名稱空間spa
全局名稱空間,局部名稱空間,內置名稱空間3d
取值順序與加載順序rest
做用域對象
內置函數:globals() locals()blog
1.3 高階函數(函數的嵌套)內存
1.4關鍵字:global nonlocal作用域
昨天咱們從形參角度,講了兩種參數,一個是位置參數,位置參數主要是實參與形參從左至右一一對應,一個是默認值參數,默認值參數,若是實參不傳參,則形參使用默認參數。那麼不管是位置參數,仍是默認參數,函數調用時傳入多少實參,我必須寫等數量的形參去對應接收, 若是不這樣,那麼就會報錯:
def eat(a,b,c,):
print('我請你吃:',a,b,c)
eat('蒸羊羔','蒸熊掌','蒸鹿尾兒','燒花鴨') # 報錯
若是咱們在傳參數的時候不很清楚有哪些的時候,或者說給一個函數傳了不少實參,咱們就要對應寫不少形參,這樣很麻煩,怎麼辦?,咱們能夠考慮使用動態參數也叫萬能參數
動態參數分爲兩種:動態接受位置參數 *args,動態接收關鍵字參數**kwargs.
咱們按照上面的例子繼續寫,若是我請你吃的內容不少,可是我又不想用多個參數接收,那麼我就可使用動態參數*args
def eat(*args):
print('我請你吃:',args)
eat('蒸羊羔兒','蒸熊掌','蒸鹿尾兒','燒花鴨','燒雛雞','燒子鵝')
# 運行結果:
#我請你吃: ('蒸羊羔兒', '蒸熊掌', '蒸鹿尾兒', '燒花鴨', '燒雛雞', '燒子鵝')
解釋一下上面參數的意義:首先來講args,args就是一個普通的形參,可是若是你在args前面加一個,那麼就擁有了特殊的意義:在python中除了表示乘號,他是有魔法的。+args,這樣設置形參,那麼這個形參會將實參全部的位置參數接收,放置在一個元組中,並將這個元組賦值給args這個形參,這裏起到魔法效果的是 * 而不是args,a也能夠達到剛纔效果,可是咱們PEP8規範中規定就使用args,約定俗成的。 練習:傳入函數中數量不定的int型數據,函數計算全部數的和並返回。
def my_max(*args):
n = 0
for i in args:
n += i
return n
動態接收關鍵字參數: **kwargs
實參角度有位置參數和關鍵字參數兩種,python中既然有*args能夠接受全部的位置參數那麼確定也有一種參數接受全部的關鍵字參數,那麼這個就是kwargs,同理這個是具備魔法用法的,kwargs約定俗成使用做爲形參。舉例說明:**kwargs,是接受全部的關鍵字參數而後將其轉換成一個字典賦值給kwargs這個形參。
def func(**kwargs):
print(kwargs) # {'name': '太白金星', 'sex': '男'}
func(name='太白金星',sex='男')
咱們看一下動態參數的完成寫法:
def func(*args,**kwargs):
print(args) # ('蒸羊羔兒', '蒸熊掌', '蒸鹿尾兒')
print(kwargs) # {'name': '太白金星', 'sex': '男'}
func('蒸羊羔兒', '蒸熊掌', '蒸鹿尾兒',name='太白金星',sex='男')
若是一個參數設置了動態參數,那麼他能夠接受全部的位置參數,以及關鍵字參數,這樣就會大大提高函數拓展性,針對於實參參數較多的狀況下,解決了一一對應的麻煩。
剛纔咱們研究了動態參數,其實有的同窗對於魔法用法 * 比較感興趣,那麼那的魔性用法不止這麼一點用法,咱們繼續研究:
函數中分爲打散和聚合。
函數外能夠處理剩餘的元素。
函數的打散和聚合
聚合
剛纔咱們研究了,在函數定義時,若是我只定義了一個形參稱爲args,那麼這一個形參只能接受幾個實參? 是否是隻能當作一個位置參數對待?它只能接受一個參數:
def eat(args):
print('我請你吃:',args) # 我請你吃: 蒸羊羔兒
eat('蒸羊羔兒')
可是若是我給其前面加一個* 那麼args能夠接受多個實參,而且返回一個元組,對吧? (**kwargs也是同理將多個關鍵字參數轉化成一個字典返回)因此在函數的定義時: *起到的是聚合的做用。
打散
此時不着急給你們講這個打散,而是出一個小題:你如何將三個數據(這三個數據都是可迭代對象類型)s1 = 'alex',l1 = [1, 2, 3, 4], tu1 = ('武sir', '太白', '女神',)的每一元素傳給動態參數*args?(就是args最終獲得的是 ('a','l','e','x', 1, 2, 3, 4,'武sir', '太白', '女神',)?有人說這還不簡單麼?我直接傳給他們不就好了?
s1 = 'alex'
l1 = [1, 2, 3, 4]
tu1 = ('武sir', '太白', '女神',)
def func(*args):
print(args) # ('alex', [1, 2, 3, 4], ('武sir', '太白', '女神'))
func(s1,l1,tu1)
這樣確定是不行,他會將這個三個數據類型當成三個位置參數傳給args,沒有實現個人要求。
好像你除了直接寫,沒有別的什麼辦法,那麼這裏就得用到咱們的魔法用法 :*
s1 = 'alex'
l1 = [1, 2, 3, 4]
tu1 = ('武sir', '太白', '女神',)
def func(*args):
print(args) # ('a', 'l', 'e', 'x', 1, 2, 3, 4, '武sir', '太白', '女神')
func(*s1,*l1,*tu1)
你看此時是函數的執行時,我將你位置參數的實參(可迭代類型)前面加上,至關於將這些實參給拆解成一個一個的組成元素當成位置參數,而後傳給args,這時候這個好像取到的是打散的做用。因此在函數的執行時:,**起到的是打散的做用。
dic1 = {'name': '太白', 'age': 18}
dic2 = {'hobby': '喝茶', 'sex': '男'}
def func(**kwargs):
print(kwargs) # {'name': '太白', 'age': 18, 'hobby': '喝茶', 'sex': '男'}
func(**dic1,**dic2)
*處理剩下的元素
*除了在函數中能夠這樣打散,聚合外,函數外還能夠靈活的運用:
# 以前講過的分別賦值
a,b = (1,2)
print(a, b) # 1 2
# 其實還能夠這麼用:
a,*b = (1, 2, 3, 4,)
print(a, b) # 1 [2, 3, 4]
*rest,a,b = range(5)
print(rest, a, b) # [0, 1, 2] 3 4
print([1, 2, *[3, 4, 5]]) # [1, 2, 3, 4, 5]
到目前爲止,從形參的角度咱們講了位置參數,默認值參數,動態參數*args,**kwargs,還差一種參數,須要講完形參順序以後,引出。先不着急,咱們先看看已經講的這些形參他的排列順序是如何的呢?
首先,位置參數,與默認參數他兩個的順序咱們昨天已經肯定了,位置參數必須在前面,即 :位置參數,默認參數。
那麼動態參數*args,**kwargs放在哪裏呢?
動態參數*args,確定不能放在位置參數前面,這樣個人位置參數的參數就接收不到具體的實參了:
# 這樣位置參數a,b始終接收不到實參了,由於args所有接受完了
def func(*args,a,b,sex='男'):
print(args)
print(a,b)
func(1, 2, 3, 4, 5)
那麼動態參數必須在位置參數後面,他能夠在默認參數後面麼?
# 這樣也不行,個人實參的第三個參數始終都會將sex覆蓋掉,這樣失去了默認參數的意義。
def func(a,b,sex='男',*args,):
print(args) # (4, 5)
print(sex) # 3
print(a,b) # 1 2
func(1, 2, 3, 4, 5)
因此*args必定要在位置參數與默認值參數中間:位置參數,*args,默認參數。
那麼個人kwargs放在哪裏?kwargs能夠放在默認參數前面麼?
# 直接報錯:由於**kwargs是接受全部的關鍵字參數,若是你想改變默認參數sex,你永遠也改變不了,由於
# 它會先被**kwargs接受。
def func(a,b,*args,**kwargs,sex='男',):
print(args) # (4, 5)
print(sex) # 3
print(a,b) # 1 2
print(kwargs)
func(1, 2, 3, 4, 5)
因此截止到此:全部形參的順序爲:位置參數,*args,默認參數,**kwargs。
僅限關鍵字參數是python3x更新的新特性,他的位置要放在*args後面,kwargs前面(若是有kwargs),也就是默認參數的位置,它與默認參數的先後順序無所謂,它只接受關鍵字傳的參數:
# 這樣傳參是錯誤的,由於僅限關鍵字參數c只接受關鍵字參數
def func(a,b,*args,c):
print(a,b) # 1 2
print(args) # (4, 5)
# func(1, 2, 3, 4, 5)
# 這樣就正確了:
def func(a,b,*args,c):
print(a,b) # 1 2
print(args) # (3, 4)
print(5)
func(1, 2, 3, 4, c=5)
這個僅限關鍵字參數從名字定義就能夠看出他只能經過關鍵字參數傳參,其實能夠把它當成不設置默認值的默認參數並且必需要傳參數,不傳就報錯。
因此形參角度的全部形參的最終順序爲:位置參數,*args,默認參數,僅限關鍵字參數,**kwargs。
課間考一道題:
def foo(a,b,*args,c,sex=None,**kwargs):
print(a,b)
print(c)
print(sex)
print(args)
print(kwargs)
# foo(1,2,3,4,c=6)
# foo(1,2,sex='男',name='alex',hobby='old_woman')
# foo(1,2,3,4,name='alex',sex='男')
# foo(1,2,c=18)
# foo(2, 3, [1, 2, 3],c=13,hobby='喝茶')
# foo(*[1, 2, 3, 4],**{'name':'太白','c':12,'sex':'女'})
接下來咱們講的內容,理論性的偏多,就是從空間角度,內存級別去研究python。首先咱們看看什麼是全局名稱空間:
在python解釋器開始執行以後, 就會在內存中開闢一個空間, 每當遇到一個變量的時候, 就把變量名和值之間的關係記錄下來, 可是當遇到函數定義的時候, 解釋器只是把函數名讀入內存, 表示這個函數存在了, 至於函數內部的變量和邏輯, 解釋器是不關心的. 也就是說一開始的時候函數只是加載進來, 僅此而已, 只有當函數被調用和訪問的時候, 解釋器纔會根據函數內部聲明的變量來進行開闢變量的內部空間. 隨着函數執行完畢, 這些函數內部變量佔用的空間也會隨着函數執行完畢而被清空.
咱們首先回憶一下Python代碼運行的時候遇到函數是怎麼作的,從Python解釋器開始執行以後,就在內存中開闢裏一個空間,每當遇到一個變量的時候,就把變量名和值之間對應的關係記錄下來,可是當遇到函數定義的時候,解釋器只是象徵性的將函數名讀如內存,表示知道這個函數存在了,至於函數內部的變量和邏輯,解釋器根本不關心。
等執行到函數調用的時候,Python解釋器會再開闢一塊內存來儲存這個函數裏面的內容,這個時候,才關注函數裏面有哪些變量,而函數中的變量回儲存在新開闢出來的內存中,函數中的變量只能在函數內部使用,而且會隨着函數執行完畢,這塊內存中的全部內容也會被清空。
咱們給這個‘存放名字與值的關係’的空間起了一個名字-------命名空間。
代碼在運行伊始,建立的存儲「變量名與值的關係」的空間叫作全局命名空間;
在函數的運行中開闢的臨時的空間叫作局部命名空間也叫作臨時名稱空間。
如今咱們知道了,py文件中,存放變量與值的關係的一個空間叫作全局名稱空間,而當執行一個函數時,內存中會臨時開闢一個空間,臨時存放函數中的變量與值的關係,這個叫作臨時名稱空間,或者局部名稱空間。
其實python還有一個空間叫作內置名稱空間:內置名稱空間存放的就是一些內置函數等拿來即用的特殊的變量:input,print,list等等,因此,咱們經過畫圖捋一下:
那麼這就是python中常常提到的三個空間。
總結:
\1. 全局命名空間--> 咱們直接在py文件中, 函數外聲明的變量都屬於全局命名空間
\2. 局部命名空間--> 在函數中聲明的變量會放在局部命名空間
\3. 內置命名空間--> 存放python解釋器爲咱們提供的名字, list, tuple, str, int這些都是內置命名空間
所謂的加載順序,就是這三個空間加載到內存的前後順序,也就是這個三個空間在內存中建立的前後順序,你想一想他們能是同時建立麼?確定不是的,那麼誰先誰後呢?咱們捋順一下:在啓動python解釋器以後,即便沒有建立任何的變量或者函數,仍是會有一些函數直接能夠用的好比abs(-1),max(1,3)等等,在啓動Python解釋器的時候,就已經導入到內存當中供咱們使用,因此確定是先加載內置名稱空間,而後就開始從文件的最上面向下一行一行執行,此時若是遇到了初始化變量,就會建立全局名稱空間,將這些對應關係存放進去,而後遇到了函數執行時,在內存中臨時開闢一個空間,加載函數中的一些變量等等。因此這三個空間的加載順序爲:內置命名空間(程序運行伊始加載)->全局命名空間(程序運行中:從上到下加載)->局部命名空間(程序運行中:調用時才加載。
取值順序就是引用一個變量,先從哪個空間開始引用。這個有一個關鍵點:從哪一個空間開始引用這個變量。咱們分別舉例說明:
# 若是你在全局名稱空間引用一個變量,先從全局名稱空間引用,全局名# 稱空間若是沒有,纔會向內置名稱空間引用。
input = 666
print(input) # 666
# 若是你在局部名稱空間引用一個變量,先從局部名稱空間引用,
# 局部名稱空間若是沒有,纔會向全局名稱空間引用,全局名稱空間在沒有,就會向內置名稱空間引用。
input = 666
print(input) # 666
input = 666
def func():
input = 111
print(input) # 111
func()
因此空間的取值順序與加載順序是相反的,取值順序知足的就近原則,從小範圍到大範圍一層一層的逐步引用。
做用域就是做用範圍, 按照生效範圍來看分爲全局做用域和局部做用域
全局做用域: 包含內置命名空間和全局命名空間. 在整個文件的任何位置均可以使用(遵循 從上到下逐⾏執行).
局部做用域: 在函數內部可使用.
做⽤域命名空間:
1. 全局做用域: 全局命名空間 + 內置命名空間
2. 局部做⽤域: 局部命名空間
這兩個內置函數放在這裏講是在合適不過的,他們就直接能夠反映做用域的內容,有助於咱們理解做用域的範圍。
globals(): 以字典的形式返回全局做用域全部的變量對應關係。
locals(): 以字典的形式返回當前做用域的變量的對應關係。
這裏一個是全局做用域,一個是當前做用域,必定要分清楚,接下來,咱們用代碼驗證:
# 在全局做用域下打印,則他們獲取的都是全局做用域的全部的內容。
a = 2
b = 3
print(globals())
print(locals())
'''
{'__name__': '__main__', '__doc__': None, '__package__': None,
'__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000001806E50C0B8>,
'__spec__': None, '__annotations__': {},
'__builtins__': <module 'builtins' (built-in)>,
'__file__': 'D:/lnh.python/py project/teaching_show/day09~day15/function.py',
'__cached__': None, 'a': 2, 'b': 3}
'''
# 在局部做用域中打印。
a = 2
b = 3
def foo():
c = 3
print(globals()) # 和上面同樣,仍是全局做用域的內容
print(locals()) # {'c': 3}
foo()
其實咱們見到了嵌套這個詞不陌生,以前咱們講過列表的嵌套,列表的嵌套就是一個列表中還有列表,可能那個列表中還有列表......那麼顧名思義,函數的嵌套,就是一個函數中,還有函數。
想要玩明白函數的嵌套,關鍵點:只要碰見了函數名+()就是函數的調用. 若是沒有就不是函數的調用,吃透這一點就算明白了。那麼咱們舉例練習:找同窗依次說出下面代碼的執行順序
# 例1:
def func1():
print('in func1')
print(3)
def func2():
print('in func2')
print(4)
func1()
print(1)
func2()
print(2)
# 例2:
def func1():
print('in func1')
print(3)
def func2():
print('in func2')
func1()
print(4)
print(1)
func2()
print(2)
# 例3:
def fun2():
print(2)
def fun3():
print(6)
print(4)
fun3()
print(8)
print(3)
fun2()
print(5)
a = 1
def func():
print(a)
func()
a = 1
def func():
a += 1 # 報錯
func()
局部做用域對全局做用域的變量(此變量只能是不可變的數據類型)只能進行引用,而不能進行改變,只要改變就會報錯,可是有些時候,咱們程序中會遇到局部做用域去改變全局做用域的一些變量的需求,這怎麼作呢?這就得用到關鍵字global: global第一個功能:在局部做用域中能夠更改全局做用域的變量。
count = 1
def search():
global count
count = 2
search()
print(count)
利用global在局部做用域也能夠聲明一個全局變量。
def func():
global a
a = 3
func()
print(a)
因此global關鍵字有兩個做用:
1,聲明一個全局變量。
2,在局部做用域想要對全局做用域的全局變量進行修改時,須要用到 global(限於字符串,數字)。
nonlocal是python3x新加的功能,與global用法差很少,就是在局部做用域若是想對父級做用域的變量進行改變時,須要用到nonlocal,固然這個用的不是不少,瞭解便可。
def add_b():
b = 42
def do_global():
b = 10
print(b)
def dd_nonlocal():
nonlocal b
b = b + 20
print(b)
dd_nonlocal()
print(b)
do_global()
print(b)
add_b()
nonlocal關鍵字舉例
nonlocal的總結:
2,在局部做用域中,對父級做用域(或者更外層做用域非全局做用域)的變量進行引用和修改,而且引用的哪層,從那層及如下此變量所有發生改變。