1. 程式人生 > >Python基礎(十)

Python基礎(十)

今日主要內容

  • 補充:傳參與引數分配區別
  • 動態傳參
  • 函式註釋
  • 名稱空間
  • 函式名的使用
  • 函式巢狀
  • 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