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

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

. 函式引數--動態傳參

  如果我們需要給⼀個函式傳參, ⽽引數⼜是不確定的. 或者我給⼀個函式傳很多引數, 我的形參就要寫很多, 很⿇煩, 怎麼辦呢. 我們可以考慮使⽤動態引數.

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

  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,表⽰在區域性作⽤域中, 調⽤⽗級名稱空間中的變數。