1. 程式人生 > >(十)函數的動態傳參,作用域

(十)函數的動態傳參,作用域

local font UNC 默認 nbsp file name 地方 本科

?. 函數參數--動態傳參

如果我們需要給?個函數傳參, ?參數?是不確定的. 或者我給?個函數傳很多參數, 我的形參就要寫很多, 很?煩, 怎麽辦呢. 我們可以考慮使?動態參數.

形參的第三種: 動態參數 動態參數分成兩種:

  1. 動態接收位置參數

?先我們先回顧?下位置參數, 位置參數, 按照位置進?傳參。

def hobby(h1,h2,h3):
    print(h1,h2,h3)
hobby(‘看書‘,‘下棋‘,‘看電影‘)

  

可以看到,現在只是輸入了三個愛好,但是每個人的愛好肯定是不止三個,或者如果只有一個愛好,那麽多出的部分就要給一個空值,這樣才不會報錯,否則,不能與形參一一 對應。所以,就需要使用動態傳參了。

def hobby(*hohhy):
    print(hohhy)
hobby(‘看書‘, ‘下棋‘, ‘看電影‘,‘聽相聲‘,‘打籃球‘)
結果:
(‘看書‘, ‘下棋‘, ‘看電影‘, ‘聽相聲‘, ‘打籃球‘)

  

在形參的前面添加一個*,這樣就表示是動態接收傳遞過來的參數了。而且這樣,可以接收任意個數的參數,想多少就多少,不傳也可以。

動態接收參數的時候要註意: 動態參數必須在位置參數後面。

再看下一段代碼。

def hobby(*hohhy,h1,h2):
    print(hohhy,h1,h2)

  

h1,h2兩個參數,放在

hobby後面,這樣設置後,再去打印,就會報錯:TypeError: hobby() missing 2 required keyword-only arguments: ‘h1‘ and ‘h2‘。意思是,兩個被要求的關鍵字參 數丟失了,也就是沒給傳。其實,很好理解,前面的*hobby,接收的是位置參數,所以前面傳遞的所有位置參數都被這個*hobby接收了,後面的h1,h2,就沒有值可接收了。也就 會報出上面的錯誤,丟失了兩個參數。那麽怎麽處理這個代碼呢,看下面的代碼:

def hobby(h1,h2,*hohhy):
    print(h1,h2,hohhy)
hobby(‘看書‘, ‘下棋‘, ‘看電影‘,‘聽相聲‘,‘打籃球‘)
結果:
看書 下棋 (‘看電影‘, ‘聽相聲‘, ‘打籃球‘)

可以看到,h1,接收了看書,h2接收了下棋,其余的三個被*hobby接收了。這樣就印證了,上面的註意點:動態參數必須在位置參數後面。

還有一個默認值參數,也是在形參處的,它的位置在哪呢?假設,輸入愛好的時候輸入一下性別,要看一下,男生和女生的愛好的不同之處。性別可以先給定一個默認值- -‘男’。如果是女生就輸入,男生,就不輸入了,使用默認值。前面說過了,默認值參數,需要放在最後,現在也是先放在最後,看看下面的代碼:

def hobby(h1,h2,*hohhy,gender = ‘男‘):
    print(h1,h2,hohhy,gender)
hobby(‘看書‘, ‘下棋‘, ‘看電影‘,‘聽相聲‘,‘打籃球‘)
結果:
看書 下棋 (‘看電影‘, ‘聽相聲‘, ‘打籃球‘) 男

可以看到,一切OK,可以正常運行,但是這個性別可不可以放在別的地方呢?再來看:

def hobby(h1,h2,gender = ‘男‘,*hohhy):
    print(h1,h2,hohhy,gender)
hobby(‘看書‘, ‘下棋‘, ‘看電影‘,‘聽相聲‘,‘打籃球‘)
結果:
看書 下棋 (‘聽相聲‘, ‘打籃球‘) 看電影 

可以看到,沒有報錯,但是仔細看會發現,有問題,本該打印男的地方,出現了‘看電影’,這是按照位置參數的方式,gender接收了‘看電影’。如果把‘看電影’改 為‘男’或 者‘女’,其實也是可以的。但這樣,設置的默認值就沒有意義了。如果既想使用默認值,還想可以改變這個參數的值,那麽,就把默認值參數放在最後。不需要 修改時,不傳 參,需要修改時,使用關鍵字傳參的方式。

因此可以得到一個小結論:位置參數, *動態參數, 默認值參數。

2. 動態接收關鍵字參數

python中可以動態的位置參數, 但是*這種情況只能接收位置參數?法接收關鍵字參數。在python中使?**就可以接收動態關鍵字參數了。看下面代碼:

def hobby(**hohhy_info):
    print(hohhy_info)

hobby(hohhy = [‘看書‘, ‘下棋‘, ‘看電影‘, ‘聽相聲‘, ‘打籃球‘], name=‘Tom‘,gender=‘男‘)
結果:
{‘hohhy‘: [‘看書‘, ‘下棋‘, ‘看電影‘, ‘聽相聲‘, ‘打籃球‘], ‘name‘: ‘Tom‘, ‘gender‘: ‘男‘}  

這就是動態接收關鍵字參數。接收後,打印出來的是個字典。

問題,又來了,現在形參有四種情況了,分別是:位置參數,*動態參數,**動態參數,默認值參數。這四個的順序是什麽呢?

順序的問題, 在函數調?的時候, 如果先給出關鍵字參數, 則整個參數列表會報錯.

def func(a, b, c, d):

 print(a, b, c, d)

# 關鍵字參數必須在位置參數後?, 否則參數會混亂

func(1, 2, c=3, 4)

所以關鍵字參數必須在位置參數後面. 由於實參是這個順序. 所以形參接收的時候也是這個順序. 也就是說位置參數必須在關鍵字參數前面。 類似的,動態接收關鍵字參數也要在後面,所以最終順序:位置參數 > *args > 默認值參數 > **kwargs

有一個現象,就是位置參數動態傳遞過去後,被*args打包成了一個元組,動態接收關鍵字參數時,**args又把所有的參數打包成了一個字典。那麽要是在調用的地方給參數帶 著“*”呢?作用與形參的地方正好相反,實參的地方使用“*”,是打散這個實參,然後再傳遞過去。

def hobby(*hohhy):
    print(hohhy)
hobby(*[‘看書‘, ‘下棋‘, ‘看電影‘, ‘聽相聲‘, ‘打籃球‘])
結果:
(‘看書‘, ‘下棋‘, ‘看電影‘, ‘聽相聲‘, ‘打籃球‘)

可以看到,這段代碼把列表開始打散了,然後再組成一個元組。再看**”打散的效果:

def hobby(**info):
    print(info)
hobby(**{‘name‘:‘Tom‘,‘age‘:23})
結果:
{‘name‘: ‘Tom‘, ‘age‘: 23}

看到結果了,很懵吧?怎麽會是一樣的呢?這是沒有處理啊。其實已經處理了:

def hobby(**info):
    print(info)
hobby(**{‘name‘: ‘Tom‘, ‘age‘: 23})
hobby(name=‘Tom‘, age = 23)
結果:
{‘name‘: ‘Tom‘, ‘age‘: 23}
{‘name‘: ‘Tom‘, ‘age‘: 23}

可以看到,兩種寫法最後的結果是一樣的。其實,第一種打散,就是打散成第二種方式了。這個過程是內部程序做的,看不到。還有一個疑問,傳進去字典,輸出的還是字典, 也有什麽用呢?再看一段代碼:

def hobby(**info):
    print(info)
hobby(**{‘name‘: ‘Tom‘, ‘age‘: 23},**{‘gender‘:‘男‘,‘edu‘:‘本科‘})
hobby(**{‘name‘: ‘Tom‘, ‘age‘: 23},gender=‘男‘,edu=‘本科‘)
結果:
{‘name‘: ‘Tom‘, ‘age‘: 23, ‘gender‘: ‘男‘, ‘edu‘: ‘本科‘}
{‘name‘: ‘Tom‘, ‘age‘: 23, ‘gender‘: ‘男‘, ‘edu‘: ‘本科‘}

可以看到兩種方式的傳參後,最後的結果是一樣的效果。這就是**”打散的作用了,可以合並兩個字典,也可以把零散的關鍵字傳參,打包進字典在中去。

二、命名空間

1.這裏介紹幾個名詞:全局命名空間,局部命名空間,內置命名空間。

1) 全局命名空間--> 我們直接在py?件中, 函數外聲明的變量都屬於全局命名空間

2) 局部命名空間--> 在函數中聲明的變量會放在局部命名空間

3) 內置命名空間--> 存放python解釋器為我們提供的名字, list, tuple, str, int這些都是內置命名空間

加載順序:

a. 內置命名空間

b. 全局命名空間

c. 局部命名空間(函數被執?的時候)

取值順序:

a. 局部命名空間

b. 全局命名空間

c. 內置命名空間

2.作?域: 作?域就是作?範圍, 按照?效範圍來看分為全局作?域和局部作?域

作?域命名空間:

a. 全局作?域: 全局命名空間 + 內置命名空間

b. 局部作?域: 局部命名空間

我們可以通過globals()函數來查看全局作?域中的內容, 也可以通過locals()來查看局部作?域中的變量和函數信息。

a = 10
def func():
   a = 40
   b = 20
   def abc():
      print("哈哈")
   print(a, b) # 這?使?的是局部作?域 (1)
   print(globals()) # 打印全局作?域中的內容(2)
   print(locals()) # 打印局部作?域中的內容(3)
func()
結果:
40 20 #(1)打印的結果
{‘__name__‘: ‘__main__‘, ‘__doc__‘: None, ‘__package__‘: None, ‘__loader__‘: <_frozen_importlib_external.SourceFileLoader object at 0x00000094AF26C240>, 
‘__spec__‘: None, ‘__annotations__‘: {}, ‘__builtins__‘: <module ‘builtins‘ (built-in)>, ‘__file__‘: ‘D:/pyworkspace/day11_大作業/test.py‘,
‘__cached__‘: None, ‘a‘: 10, ‘func‘: <function func at 0x00000094AF1B1EA0>} #(2)打印的結果 {‘abc‘: <function func.<locals>.abc at 0x00000094AF3310D0>, ‘b‘: 20, ‘a‘: 40} #(3)打印的結果 

通過結果,可以看出(2)和(3)打印的是兩個字典,尤其(3)中可以看到 “ ‘b‘: 20, ‘a‘: 40”,這是標準的鍵值對的形式。globals()locals(),兩個函數時Python內置的, 為的就是查看,當前作用域中變量有哪些。globals(),查看的是.py文件中的全局的,locals()查看的是當前作用域中,變量有哪些。

. 函數的嵌套

1. 只要遇?了()就是函數的調?. 如果沒有()就不是函數的調?

2. 函數的執?順序:走到某個函數時,會先把代碼加載到內存中,然後等待著被調用。被調用到後,再去執行函數裏面的代碼。

函數的調用:

def fun1():
 print(111)
 
def fun2():
 print(222)
 fun1()
 
fun2()
 函數的嵌套:
def fun2():
   print(222)
   def fun3():
      print(666)
   print(444)
   fun3()
   print(888)
print(33)
fun2()
print(555)  

函數的嵌套的意義在哪呢?後面的閉包和裝飾器會顯露出來,拭目以待。

. 關鍵字globalnonlocal

首先我們寫這樣?個代碼, ?先在全局聲明?個變量, 然後再局部調?這個變量, 並改變這個變量的值。

a = 100
def func():
   global a # 加了個global表示不再局部創建這個變量了. ?是直接使?全局的a
   a += 28
   print(a)
func()
print(a)

  

a = 10
def func1():
   a = 20
   def func2():
      nonlocal a
      a = 30
      print(a)
   func2()
   print(a)
func1()
結果:
加了nonlocal
30
30
不加nonlocal
30
20 

nonlocal的作用就是使用離本作用域最近的上一層的變量,上一層沒有,再繼續上一層,知道找到最外層的函數,如果都沒有,就會報錯。

總結一下,這兩個關鍵字的調用順序。

global局部作用域-->全局作用域-->內置作用域

nonlocal最內層函數-->上一層函數-->...-->最外層函數

上面的代碼這樣寫,運行後結果:128,128。但是如果把global這一行去掉後,直接報紅。根本沒有辦法編譯,在語法上就是錯誤的。那為什麽帶著global就可以呢?因為,使用這個關鍵字global後,就把全局的a帶到了函數內,在函數內就可以修改這個a的值了。這就是global的作用:在局部使用並且可以修改全局的變量。也可以說是不再使?局部作?域中的內容了, ?改?全局作?域中的變量。

接下來再看nonlocal,表?在局部作?域中, 調??級命名空間中的變量。

(十)函數的動態傳參,作用域