記錄個人 python 學習歷程-Day10 函數進階

函數的參數Ⅱ

  • 形參的第三種:動態參數

    動太參數分爲兩種:動態接收位置參數:*args;動態接收關鍵字參數:**kwargspython

    • **動態接收位置參數:*args**函數

      def msg(*args):
          print('你的信息爲:', args)
      
      msg('name', 111, False, [1, 3, 4, 5])
      
      # 運行結果:你的信息爲: ('name', 111, False, [1, 3, 4, 5])

      ​ 解釋一下上面參數的意義:首先來講args,args就是一個普通的形參,可是若是你在args前面加一個,那麼就擁有了特殊的意義:在python中除了表示乘號,他是有魔法的。加 args,這樣設置形參,那麼這個形參會將實參全部的位置參數接收,放置在一個元組中,並將這個元組賦值給args這個形參,這裏起到魔法效果的是 * 而不是args,a也能夠達到剛纔效果,可是咱們PEP8規範中規定就使用args,約定俗成的。ui

      • 練習:傳入函數中數量不定的 Int 型數據,函數計算全部數的和並返回。
      # 傳入函數中數量不定的int型數據,函數計算全部數的和並返回。
      def cont(*args):
          n = 0
          for i in args:
              n += i
          return n
      
      cont = cont(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
      print(cont)
      
      # 運行結果: 55
    • 動態接收關鍵字參數: ****** kwargsrest

      ​ 實參角度有位置參數和關鍵字參數兩種,python中既然有*args能夠接受全部的位置參數那麼確定也有一種參數接受全部的關鍵字參數,那麼這個就是 kwargs,同理這個 是具備魔法用法的,kwargs約定俗成使用做爲形參。舉例說明:**kwargs,是接受全部的關鍵字參數而後將其轉換成一個 '字典' 賦值給 kwargs 這個形參。code

      def func(**kwargs):
          print(kwargs)
      
      func(name='Dylan', age=18, sex='男')
      
      # 運行結果:{'name': 'Dylan', 'age': 18, 'sex': '男'}
    • 動態形參的完整寫法內存

      def fun(*args, **kwargs):
          print(args)
          print(kwargs)
      
      fun('週一', '週二', '週三', '週四', name='Dylan', age=18, sex='男')
      
      # 運行結果:
      # ('週一', '週二', '週三', '週四')
      # {'name': 'Dylan', 'age': 18, 'sex': '男'}

      ​ 若是一個參數設置了動態參數,那麼他能夠接收全部的位置參數,以及關鍵字參數,這樣就會大大提高函數拓展性,針對於實參參數較多的狀況下,解決了一一對應的麻煩。作用域

  • *** 的魔性用法**

    • 函數中分爲打散和聚合。input

      • 聚合源碼

        在函數定義時,哪果我只定義了一個形參稱爲:args,那麼這一個形參只能接受一個實參:io

        def msg(args):
            print('你的信息爲:', args)
        
        msg('name')
        
        # 運行結果:你的信息爲: name

        若是給其前面加一個 * 號,那麼 args 就能夠接收多個實參,而且返回一個元組(**kwargs 也是同理,將多個關鍵字參數轉化成一個字典返回)
        因此:
        在函數定義時, ’號起到的是聚合的做用。

      • 打散

        s = 'Dylan'
        li = [1, 2, 3, 4]
        tu = ('Name = Dylan', 'age = 18', 'sex = 男')
        
        def func(*args):
            print(args)
        
        func(*s, *li, *tu)
        
        # 運行結果:('D', 'y', 'l', 'a', 'n', 1, 2, 3, 4, 'Name = Dylan', 'age = 18', 'sex = 男')

        ​ 函數執行時,我將你位置參數的實參(可迭代類型)前面加上 * 或者 ** ,至關於將這些實參給拆解成一個一個的組成元素當成位置參數,而後傳給 args或者 kwargs。
        因此:在函數的執行時,* 或者 ** 起到的是打散的做用。

        dic1 = {'name': 'Dylan', 'age': 18, 'sex': '男'}
        dic2 = {'0': 11, '1': 22, '2': 33}
        
        def func(**kwargs):
            print(kwargs)
        
        func(**dic1, **dic2) #
        
        # 運行結果:{'name': 'Dylan', 'age': 18, 'sex': '男', '0': 11, '1': 22, '2': 33}
    • 函數外能夠處理剩餘的元素。

      除了在函數中能夠這樣打散,聚合外,函數外還能夠靈活的運用:

      # 以前講過的分別賦值
      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,確定不能放在位置參數前面,這樣個人位置參數的參數就接收不到具體的實參了:

    # 這樣位置參數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是接受全部的關鍵字參數,若是你想改變默認參數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, age=80, sex='666')

    因此截止到此:全部形參的順序爲:
    位置參數,args,默認參數, * kwargs。

  • 形參的第四種參數:僅限關鍵字參數

    僅限關鍵字參數是python3x更新的新特性,他的位置要放在*args後面, kwargs前面(若是有 kwargs),也就是默認參數的位置,它與默認參數的先後順序無所謂,它只接受關鍵字傳的參數:

    def func(a,b,*args,sex= '男',c,**kwargs,):
        print(a,b)
        print(sex)
        print(args)
        print(c)
        print(kwargs)
    
    func(1,2,3,4,5,6,7,sex='女',name='Alex',age=80,c='666')
    
    """
    輸出結果:
    1 2
    女
    (3, 4, 5, 6, 7)
    666
    {'name': 'Alex', 'age': 80}
    """

    這個僅限關鍵字參數從名字定義就能夠看出他只能經過關鍵字參數傳參,其實能夠把它當成不設置默認值的默認參數並且必需要傳參數,不傳就報錯。

    因此形參角度的全部形參的最終順序爲:
    **位置參數,args,默認參數,僅限關鍵字參數, *kwargs。**

名稱空間和做用域

  • 名稱空間

    ​ 在python解釋器開始執行以後, 就會在內存中開闢一個空間, 每當遇到一個變量的時候, 就把變量名和值之間的關係記錄下來, 可是當遇到函數定義的時候, 解釋器只是把函數名讀入內存, 表示這個函數存在了, 至於函數內部的變量和邏輯, 解釋器是不關心的. 也就是說一開始的時候函數只是加載進來, 僅此而已, 只有當函數被調用和訪問的時候, 解釋器纔會根據函數內部聲明的變量來進行開闢變量的內部空間. 隨着函數執行完畢, 這些函數內部變量佔用的空間也會隨着函數執行完畢而被清空.

    ​ 咱們首先回憶一下Python代碼運行的時候遇到函數是怎麼作的,從Python解釋器開始執行以後,就在內存中開闢裏一個空間,每當遇到一個變量的時候,就把變量名和值之間對應的關係記錄下來,可是當遇到函數定義的時候,解釋器只是象徵性的將函數名讀如內存,表示知道這個函數存在了,至於函數內部的變量和邏輯,解釋器根本不關心。

    ​ 等執行到函數調用的時候,Python解釋器會再開闢一塊內存來儲存這個函數裏面的內容,這個時候,才關注函數裏面有哪些變量,而函數中的變量會儲存在新開闢出來的內存中,函數中的變量只能在函數內部使用,而且會隨着函數執行完畢,這塊內存中的全部內容也會被清空。

    ​ 咱們給這個‘存放名字與值的關係’的空間起了一個名字:命名空間

    代碼在運行伊始,建立的存儲「變量名與值的關係」的空間叫作全局命名空間

    在函數的運行中開闢的臨時的空間叫作局部命名空間也叫作臨時名稱空間

    ​ 如今咱們知道了,py文件中,存放變量與值的關係的一個空間叫作全局名稱空間,而當執行一個函數時,內存中會臨時開闢一個空間,臨時存放函數中的變量與值的關係,這個叫作臨時名稱空間,或者局部名稱空間。

    ​ 其實python還有一個空間叫作內置名稱空間:內置名稱空間存放的就是一些內置函數等拿來即用的特殊的變量:input,print,list等等

    總結:

    • 全局命名空間--> 咱們直接在py文件中, 函數外聲明的變量都屬於全局命名空間

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

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

      # 內置名稱空間:python源碼給你提供的一些內置的函數,print input
      # print(666)
      # python分爲三個空間:
          # 內置名稱空間(builtins.py)
          # 全局名稱空間(當前py文件)
          # 局部名稱空間(函數,函數執行時纔開闢)
  • 加載順序

    ​ 所謂的加載順序,就是這三個空間加載到內存的前後順序,也就是這個三個空間在內存中建立的前後順序,你想一想他們能是同時建立麼?確定不是的,那麼誰先誰後呢?咱們捋順一下:在啓動python解釋器以後,即便沒有建立任何的變量或者函數,仍是會有一些函數直接能夠用的好比abs(-1),max(1,3)等等,在啓動Python解釋器的時候,就已經導入到內存當中供咱們使用,因此確定是先加載內置名稱空間,而後就開始從文件的最上面向下一行一行執行,此時若是遇到了初始化變量,就會建立全局名稱空間,將這些對應關係存放進去,而後遇到了函數執行時,在內存中臨時開闢一個空間,加載函數中的一些變量等等。
    因此這三個空間的加載順序爲:
    內置命名空間(程序運行伊始加載)->全局命名空間(程序運行中:從上到下加載)->局部命名空間(程序運行中:調用時才加載)。

    # 加載順序:
    # 內置名稱空間 ---> 全局名稱空間  ----> 局部名稱空間(函數執行時)
    def func():
        pass
    func()
    a = 5
    print(666)
  • 取值順序

    ​ 值順序就是引用一個變量,先從哪個空間開始引用。這個有一個關鍵點:從哪一個空間開始引用(注意:只可引用,不可更改上一層的變量)這個變量。咱們分別舉例說明:

    # 若是你在全局名稱空間引用一個變量,先從全局名稱空間引用,全局名稱空間若是沒有,纔會向內置名稱空間引用。
    input = 666
    print(input) # 666
    
    # 若是你在局部名稱空間引用一個變量,先從局部名稱空間引用,局部名稱空間若是沒有,纔會向全局名稱空間引用,全局名稱空間在沒有,就會向內置名稱空間引用。
    input = 666
    print(input) # 666
    input = 666
    def func():
        input = 111
        print(input) # 111
    func()

    下面這個例子比較清晰

    # 取值順序(就近原則) 單向不可逆
    # LEGB原則
    input = 'Dylan'
    
    def func():
        input = 'xiaoyu'
        print(input)    # xiaoyu
    
    func()
    
    # (從局部找時)局部名稱空間  ---> 全局名稱空間  --->  內置名稱名稱空間
    
    input = 'Dylan'
    def func():
        print(input)    # Dylan
    
    func()
    print(input)    # Dylan

    因此空間的取值順序與加載順序是相反的,取值順序知足的就近原則,從小範圍到大範圍一層一層的逐步引用。

  • 做用域

    做用域就是做用範圍, 按照生效範圍來看分爲全局做用域和局部做用域

    • 全局做用域: 包含內置命名空間和全局命名空間. 在整個文件的任何位置均可以使用(遵循 從上到下逐⾏執行).

    • 局部做用域: 在函數內部可使用.

    做⽤域命名空間:

    1. 全局做用域: 全局命名空間 + 內置命名空間

    2. 局部做⽤域: 局部命名空間

  • 內置函數globals(),locals()

    這兩個內置函數放在這裏講是在合適不過的,他們就直接能夠反映做用域的內容,有助於咱們理解做用域的範圍。

    • 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')   # 1
    print(3)    # 2
def func2():
    print('in func2')   # 4
    print(4)    # 5
func1()
print(1)    # 3
func2()
print(2)    # 6

# 例2:
def func1():
    print('in func1')   # 3
    print(3)    # 4
def func2():
    print('in func2')   # 2
    func1()
    print(4)    # 5
print(1)    # 1
func2()
print(2)    # 6
# 例3:
def fun2(): 
    print(2)    # 2
    def fun3(): 
        print(6)    # 4
    print(4)    # 3
    fun3() 
    print(8)    # 5
print(3)    # 1
fun2()
print(5)    # 6
相關文章
相關標籤/搜索