Python基礎(十)

  • 2019 年 10 月 3 日
  • 筆記

今日主要內容

  • 補充:傳參與參數分配區別
  • 動態傳參
  • 函數注釋
  • 名稱空間
  • 函數名的使用
  • 函數嵌套
  • global和nonlocal

補充:傳參與參數分配區分

  • 先看一個函數定義和函數調用

    def func(a, b):    print(b, a)    a = 5  b = 10  func(b, a)  # 5 10

    • 粉色箭頭是傳參的過程
    • 青色箭頭是分配參數的過程
  • 傳參的過程按照參數類型進行,位置參數按順序一一對應傳參,與變數名是什麼無關

  • 而參數分配是函數自己的事,函數體屬於函數,分配參數的過程要按照變數名分配

一、動態傳參

(一) 為什麼要用動態傳參

  • 先來看一個例子:

    • 定義一個吃飯的函數,每次調用時傳入要吃的東西,列印菜單
    def eat(zhushi, huncai, sucai, tang, tiandian):    print("我要吃:", zhushi, huncai, sucai, tang, tiandian)    eat("大米飯", "紅燒肉", "燒茄子", "番茄湯", "慕斯")  # 我要吃: 米飯 紅燒肉 燒茄子 番茄湯 慕斯
    • 這可能是我的飯量,但是這時來了一個女生,女生吃不了這麼多啊,只選了一個小花捲,一盤黃瓜炒雞蛋
    def eat(zhushi, huncai, sucai, tang, tiandian):    print("我要吃:", zhushi, huncai, sucai, tang, tiandian)    eat("小花捲", "黃瓜炒雞蛋")  # TypeError: eat() missing 3 required positional arguments: 'sucai', 'tang', and 'tiandian'
    • 由於我填入的參數與定義的參數數量不對應,調用時就會報錯,怎麼解決這個事情呢,這就要用到了我們的動態傳參。
  • 動態傳參可以給函數傳入不定量的參數,以元組、字典的方式打包起來,這就解決了我們填入參數是數量不定引發的error,下面我們來看看動態傳參具體的方法

(二)兩類動態傳參

  • 動態位置參數
  • 動態關鍵字參數
  1. 動態位置參數(動態接收位置參數)

    • 先來回憶一下位置參數,按照一一對應的位置來傳參
    def eat(zhushi, tang):  # 按照位置接收參數(形參)   print(f"主食:{zhushi} 湯:{tang}")    eat("米飯", "番茄蛋花湯")  # 位置參數(實參)  # 主食:米飯 湯:番茄蛋花湯
    • 動態接收位置參數,在形參前加一個星號*,表示可接收任意數量的形參,以元組存儲
    def eat(*food):  # 參數前加一個星號,表示動態接收位置參數   print(food)    eat("米飯", "番茄蛋花湯")  # 可以填寫任意數量參數  # ('米飯', '番茄蛋花湯')
    • 動態位置參數在python中一般用*args來表示,只是一個行業內的規範,也可以用其他變數名定義但不建議使用
    def eat(*args):  # 一般用*args表示動態位置參數   print(args)    eat("米飯", "番茄蛋花湯")  # 可以填寫任意數量參數  # ('米飯', '番茄蛋花湯')
  2. 結合之前兩種形參(位置參數、默認值參數)共同分析

    • 位置參數 + 動態位置參數

      • 位置參數必須在動態位置參數前(位置參數 > 動態位置參數)
      def eat(zhushi, *food):     print(zhushi, food)    eat("米飯", "紅燒肉", "排骨", "燒雞")  # 米飯 ('紅燒肉', '排骨', '燒雞')
      • 原因:如果動態位置參數(帶星的)在前,那麼所有實參全部都傳給了動態位置參數(帶星的),後面的位置參數(不帶星的)是接收不到任何實參的,導致報錯
      def eat(*food, zhushi):     print(food, zhushi)    eat("米飯", "    紅燒肉", "排骨", "燒雞")  # TypeError: eat() missing 1 required keyword-only argument: 'zhushi'
    • 默認值參數 + 動態位置參數

      • 默認參數必須在動態位置參數後( 動態位置參數 > 默認值參數)
      def eat(*food, zhushi="米飯"):     print(food, zhushi)    eat("回鍋肉", "紅燒肉", "翡翠白玉湯")  # ('回鍋肉', '紅燒肉', '翡翠白玉湯') 米飯  
      • 原因:默認值參數的目的就是為了在重複輸入情況下使用默認值,省去每次輸入一樣的參數,若默認值參數在前,則每次調用都需要填入參數,默認值參數就沒有意義了
      def eat(zhushi="米飯", *food):     print(zhushi, food)    eat("米飯", "回鍋肉", "紅燒肉", "翡翠白玉湯")  # 默認值參數沒有意義  # 米飯 ('回鍋肉', '紅燒肉', '翡翠白玉湯')  
    • 位置參數 + 默認值參數 + 動態位置參數

      • 參數順序:位置參數 > 動態位置參數 > 默認值參數
      def eat(zhushi, *food, tang="番茄蛋花湯"):     print(zhushi, food, tang)    eat("米飯", "紅燒肉", "排骨", "燒雞")  # 米飯 ('紅燒肉', '排骨', '燒雞') 番茄蛋花湯  
  3. 動態關鍵字參數(動態接收關鍵字參數)

    • 星號*可以接收任意數量的位置參數,但是無法接收任意數量的關鍵字參數,在形參前加兩個星號**就可以接受任意數量的關鍵字參數,以字典存儲
    def eat(zhushi, **cai):   print(f"主食:{zhushi} 菜:{cai}")    eat("米飯", cai1="紅燒肉", cai2="可樂雞翅")  主食:米飯 菜:{'cai1': '紅燒肉', 'cai2': '可樂雞翅'}  
    • 動態關鍵字參數在python中一般用**kwargs來表示,只是一個行業內的規範,也可以用其他變數名定義但不建議使用
    def eat(zhushi, **kwargs):   print(f"主食:{zhushi} 菜:{kwargs}")    eat("米飯", cai1="紅燒肉", cai2="可樂雞翅")  主食:米飯 菜:{'cai1': '紅燒肉', 'cai2': '可樂雞翅'}  
  4. 結合之前三種形參(位置參數、動態位置參數、默認值參數)共同分析

    • 回想一下實參的順序,給入實參時,位置參數必須要在關鍵字參數前,否則會報錯
    def func(a, b, c):   print(a, b, c)    func(1, b=2, 3)  # SyntaxError: positional argument follows keyword argument  
    • 所以關鍵字參數必須要在位置參數後,由於實參是這個順序,所以形參在接收的時候也必須是這個順序,所以動態關鍵字參數也必須在動態位置參數後
    def eat(zhushi, *args, tang="番茄蛋花湯", **kwargs):   print(f"主食:{zhushi} 甜品:{args} 湯:{tang} 菜:{kwargs}")    eat("米飯", "慕斯", "布丁", recai="可樂雞翅", liangcai="大拌菜")  # 主食:米飯 甜品:('慕斯', '布丁') 湯:番茄蛋花湯 菜:{'recai': '可樂雞翅', 'liangcai': '大拌菜'}  
  5. 形參的最終順序:(重要)

  • 位置參數 > 動態位置參數 > 默認值參數 > 動態關鍵字參數
  1. 星號*的作用

    • 在函數定義的時候:聚合
    • 動態位置參數聚合成元組
    • 動態關鍵字參數聚合成字典
    def func(a, b, *args):  # 將我傳入的3,4,5聚合成一個元組   print(a, b, args)    func(1, 2, 3, 4, 5)  # 1 2 (3, 4, 5)  
    • 在函數調用的時候:打散
    • 動態位置參數打散獲取的是單個元素
    • 動態關鍵字參數打散獲取的是字典的鍵
    def func(a, b, *args):  # 將我傳入的3,4,5聚合成一個元組   print(a, b, *args)  # 將元組打散成元素輸出    func(1, 2, 3, 4, 5)  # 1 2 3 4 5  
  2. 無敵傳參

    • 可以傳入任意類型參數、任意數量參數,無敵
    def func(*args, **kwargs):   print(args, kwargs)    func(1, 2, 3, k1=v1, k2=v2)  # (1, 2, 3) {'k1': 'v1', 'k2': 'v2'}  

二、函數注釋

(一)函數注釋的好處

  • 程式碼永遠是寫給人看的,在函數中添加註釋可以讓人清楚明了的了解函數的作用以及函數的用法
  • 防止自己遺忘自己寫的函數是幹什麼用的,當忘記的時候看注釋就好了
  • 可以在注釋中明確變數以及返回值建議的數據類型

(二)注釋用法

  1. 在函數體的第一行用多行注釋""" """,pycharm在函數中添加註釋默認出現函數所用到的所有變數

    def func(args1, args2):      """        :param args1: int      :param args2: int      :return: int      """      print(args1)      print(args2)      return args1, args2  

  2. 在函數定義的時候,形參後面可以加冒號並寫出建議傳入的數據類型

    def func(args1: int, args2: int):      """      此處添加註釋內容      :param args1: int      :param args2: int      :return: int      """      print(args1)      print(args2)      return args1, args2  

(三)關於函數的兩個查看方法

  1. 查看函數的注釋

    • 函數名.__doc__,若無注釋返回None
    def func(args1, args2):      """      進行加法運算      :param args1: int      :param args2: int      :return: int      """      return args1 + args2      print(func.__doc__)  
    運行結果:        進行加法運算      :param args1: int      :param args2: int      :return: int    
  2. 查看函數的名字

    • 函數名.__name__
    def func(args1, args2):      """      進行加法運算      :param args1: int      :param args2: int      :return: int      """      return args1 + args2      add = func  print(add.__name__)  # 運行結果:func  

三、名稱空間(命名空間)

(一)什麼是名稱空間

  • Python解釋器開始執行之後,就會在記憶體中開闢了一個空間,每當遇到一個變數的時候,就會把變數名(記憶體地址)和值之間的關係記錄下來,但是當遇到函數定義的時候,解釋器只是把函數名(函數的記憶體地址)讀入記憶體,表示這個函數存在了,而函數內部的變數和邏輯,解釋器是不關心的,也就是說一開始的時候函數只是載入進來了,僅此而已,只有當函數被調用和訪問的時候,解釋器才會根據函數內部聲明的變數開進行開闢變數的內部空間,隨著函數執行完畢,這些函數內部變數佔用的空間也會隨著函數執行完畢被清空,我們給存放名字和值的關係的空間起一個名字叫:名稱空間(命名空間),我們的變數在存儲的時候就是存儲在這片空間中的。(出自邱彥濤老師,我的偶像)

(二)名稱空間的分類

  • 在python中名稱空間分為三部分:
    • 內置空間
    • 全局空間
    • 局部空間
  1. 內置空間:存放python解釋器為我們提供的名字,len、print、global等等

  2. 全局空間:用來存放py文件頂格運行時聲明的變數

  3. 局部空間:用來存放在函數運行時聲明的變數

  4. 名稱空間載入順序:

    • 內置名稱空間 > 全局名稱空間 > 局部名稱空間

  5. 變數取值順序:

    • 局部名稱空間 > 全局名稱空間 > 內置名稱空間

(三)作用域

  • 作用域就是作用範圍,作用域分為兩類:
    • 全局作用域
    • 局部作用域
  1. 全局作用域:整個文件的任何位置都可以使用

    • 包含:內置名稱空間 + 全局名稱空間
    • globals()函數可以查看全局作用域中的變數和函數資訊
    a = 10  b = 20      def func(args1, args2):      print(args1)      print(args2)      print(globals())      return None      func(30, 40)  
    運行結果:  {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000001B61F2786D8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'D:/python_S26/day10/exercise.py', '__cached__': None, 'a': 10, 'b': 20, 'func': <function func at 0x000001B61D671EA0>}  
  2. 局部作用域:在函數內部可以使用

    • 包含:局部名稱空間
    • locals()函數可以查看當前作用域中的變數和函數資訊(建議查看局部)
    a = 10  b = 20      def func(args1, args2):      print(args1)      print(args2)      print(locals())      return None      func(30, 40)  
    運行結果:  30  40  {'args2': 40, 'args1': 30}  

四、函數名的使用

(一)作為值

  • 函數名可以當作值,賦值給另一個變數

    def func():      print("zxd")      name = func  # 函數名賦值給另一個變數  print(func)  print(name)  name()    運行結果:  <function func at 0x000002161A261EA0>  <function func at 0x000002161A261EA0>  zxd  

(二)作為參數

  • 函數名可以當作另一個函數的參數來使用

    def func():      print("zxd")      def name(args):      print(args)  # <function func at 0x00000299B5811EA0>      args()      name(func)    運行結果:  <function func at 0x00000299B5811EA0>  zxd  

(三)作為返回值

  • 函數名可以當作另一個函數的返回值來使用

    def name():      print("zxd")      def func():      return name      func()()    運行結果:  zxd  

(四)作為容器中的元素

  • 函數名可以當作元素存儲在容器中

    def login():      print("登錄")    def register():      print("註冊")    def look():      print("瀏覽商品")    def buy():      print("購買商品")    msg = """  1.註冊  2.登錄  3.瀏覽商品  4.購買商品  請選擇序號:"""    dic_func = {      "1": register,      "2": login,      "3": look,      "4": buy,  }  while True:      num = input(msg)      if num in dic_func.keys():          dic_func[num]()      else:          print("輸入錯誤!")  

五、函數嵌套

(一)交叉嵌套

  • 函數參數作為另一個函數的參數,從而插入另一個函數中

  • 看一個例子:

    def func1(a):      print(1)      a()      print(2)    def func2():      print(3)      re = func3()      print(re)      print(4)    def func3():      print(5)    func1(func2)    運行結果:  1 3 5 None 4 2  
  • 運行順序

(二)內部嵌套

  • 函數內部嵌套函數

  • 看一個例子

    def func1():      print(1)      def func2():          print(2)          def func3():              print(3)          func3()          print(4)      func2()      print(5)    func1()    運行結果:  1 2 3 4 5  
  • 運行順序

六、global和nonlocal

(一)global

  • 全局中的變數在函數內部只有使用權,可以拿來用,但是不能更改

    num = 10  def func():      num = num + 1      print(num)    func()  print(num)  # UnboundLocalError: local variable 'num' referenced before assignment  
  • 如果想在局部中修改全局變數,必須先用global聲明要修改的全局變數

    num = 10  def func():      global num      num = num + 1      print(num)  # 11    func()  print(num)  # 11  
  • 在函數中使用了global聲明了變數,但全局空間中並沒有這個變數時,global會在全局空間中開闢這個變數

    def func():      global num      num = 10      print(num)  # 10    func()  print(num)  # 10  
  • 總結:

    • 可以在局部空間修改全局空間的是變數
    • 若全局空間無聲明的變數則創建該變數
    • global有效的控制因誤操作在局部空間修改全局空間的變數

(二)nonlocal

  • nonlocal修改離nonlocal最近的上一層名稱空間的變數,但只修改局部空間中的變數

    num = 10  def func1():      num = 20      def func2():          num = 30          def func3():              nonlocal num              num += 1              print(num)          func3()          print(num)      func2()      print(num)    func1()  print(num)    運行結果:  31 31 20 10  
  • nonlocal聲明的變數如果是全局空間中的變數就會報錯,並且nonlocal不會創建變數

    num = 10  def func1():      def func2():          def func3():              nonlocal num  # 局部空間內沒有變數num              print(num)          func3()          print(num)      func2()      print(num)    func1()  print(num)    運行結果:  SyntaxError: no binding for nonlocal 'num' found  
  • 當前空間如果有變數,在去用nonlocal聲明該變數,會報錯,nonlocal只能聲明上一層名稱空間的變數

    num = 10  def func1():      def func2():          def func3():            num = 10              nonlocal num  #              print(num)          func3()          print(num)      func2()      print(num)    func1()  print(num)    運行結果:  SyntaxError: name 'num' is assigned to before nonlocal declaration