1. 程式人生 > >Python第四周 學習筆記(1)

Python第四周 學習筆記(1)

學習筆記

函數
  • Python的函數沒有return語句,隱式會返回一個None值
  • 函數是可調用的對象,callable()

函數參數

  • 參數調用時傳入的參數要和定義的個數相匹配(可變參數例外)
  • 位置參數
    • def f(x, y, z) 調用使用 f(1, 3, 5)
    • 按照參數定義順序傳入實參
  • 關鍵字參數
    • def f(x, y, z) 調用使用 f(x=1, y=3, z=5)
    • 使用形參的名字來出入實參的方式,如果使用了形參名字,那麽傳參順序就可和定義順序不同
  • 傳參
    • 要求位置參數必須在關鍵字參數之前傳入,位置參數是按位置對應的

函數參數默認值

  • 定義時,在形參後跟上一個值
  • 作用
    • 參數的默認值可以在未傳入足夠的實參的時候,對沒有給定的參數賦值為默認值
    • 參數非常多的時候,並不需要用戶每次都輸入所有的參數,簡化函數調用

可變參數

  • 一個形參可以匹配任意個參數

  • 在形參前使用*表示該形參是可變參數,可以接收多個實參
  • 收集多個實參為一個tuple

  • 關鍵字參數的可變參數

    • 形參前使用**符號,表示可以接收多個關鍵字參數
    • 收集的實參名稱和值組成一個字典
  • 有位置可變參數和關鍵字可變參數
  • 位置可變參數在形參前使用一個星號*
  • 關鍵字可變參數在形參前使用兩個星號**
  • 位置可變參數和關鍵字可變參數都可以收集若幹個實參,位置可變參數收集形成一個tuple,關鍵字可變參數收集形成一個dict
  • 混合使用參數的時候,可變參數要放到參數列表的最後,普通參數需要放到參數列表前面,位置可變參數需要在關鍵字可變參數之前

keyword-only參數

  • 如果在一個星號參數後,或者一個位置可變參數後,出現的普通參數,實際上已經不是普通的參數了,而是keyword-only參數
def fn(*args, x):
print(x)
print(args)
def(**kwargs, x):
print(x)
print(kwargs)

直接報語法錯誤
可以理解為kwargs會截獲所有的關鍵字參數,就算你寫了x=5,x也永遠得不到這個值,所以語法錯誤

  • keyword-only 參數另一種形式
    def fn(*, x,y):
    print(x,y)
    fn(x=5,y=6)

    *號之後,普通形參都變成了必須給出的keyword-only 參數

可變參數和參數默認值

def fn(*args, x=5):
print(x)
print(args)

參數規則

  • 參數列表參數一般順序是,普通參數、缺省參數、可變位置參數、keyword-only參數(可帶缺省值)、可變關鍵字參數

參數解構

  • 給函數提供實參的時候,可以在集合類型前使用*或者**,把集合類型的結構解開,提取出所有元素作為函數的實參
  • 非字典類型使用*解構成位置參數
  • 字典類型使用**解構成關鍵字參數
  • 提取出來的元素數目要和參數的要求匹配,也要和參數的類型匹配

  • 參數解構和可變參數
    • 給函數提供實參的時候,可以在集合類型前使用*或者**,把集合類型的結構解開,提取出所有元素作為函數的實參

函數返回值與作用域


函數的返回值

  • Python函數使用return語句返回“返回值”
  • 所有函數都有返回值,如果沒有return語句,隱式調用return None
  • return 語句並不一定是函數的語句塊的最後一條語句
  • 一個函數可以存在多個return語句,但是只有一條可以被執行。如果沒有一條return語句被執行到,隱式調用return None
  • 如果有必要,可以顯示調用return None,可以簡寫為return
  • 如果函數執行了return語句,函數就會返回,當前被執行的return語句之後的其它語句就不會被執行了
  • 作用:結束函數調用、返回值

  • 返回多個值
    • 函數不能同時返回多個值
    • return [1, 3, 5] 是指明返回一個列表,是一個列表對象
    • return 1, 3, 5 看似返回多個值,隱式的被python封裝成了一個元組
      def showlist():
      return 1, 3, 5
      x, y, z = showlist() # 使用解構提取更為方便

函數嵌套

  • 在一個函數中定義了另外一個函數
    • 函數有可見範圍,這就是作用域的概念
    • 內部函數不能在外部直接使用,會拋NameError異常,因為它不可見

作用域

  • 全局作用域

    • 在整個程序運行環境中都可見
  • 局部作用域

    • 在函數、類等內部可見
    • 局部變量使用範圍不能超過其所在的局部作用域
  • 外層變量作用域在內層作用域可見
  • 內層作用域inner中,如果定義了o=97,相當於當前作用域中重新定義了一個新的變量o,但是這個o並沒有覆蓋外層作用域outer中的o

  • 全局變量global
    #x = 5
    def foo():
    global x
    x = 10
    x += 1 # 報錯嗎?
    print(x) # 打印什麽?
    print(x) #打印什麽?
  • 使用global關鍵字的變量,將foo內的x聲明為使用外部的全局作用域中定義的x
  • 但是,x = 10 賦值即定義,在內部作用域為一個外部作用域的變量x賦值,不是在內部作用域定義一個新變量,所以x+=1不會報錯。註意,這裏x的作用域還是全局的

  • x+=1這種是特殊形式產生的錯誤的原因?先引用後賦值,而python動態語言是賦值才算定義,才能被引用。解決辦法,在這條語句前增加x=0之類的賦值語句,或者使用global 告訴內部作用域,去全局作用域查找變量定義
  • 內部作用域使用x = 5之類的賦值語句會重新定義局部作用域使用的變量x,但是,一旦這個作用域中使用global聲明x為全局的,那麽x=5相當於在為全局作用域的變量x賦值

  • global使用原則
    • 外部作用域變量會內部作用域可見,但也不要在這個內部的局部作用域中直接使用,因為函數的目的就是為了封裝,盡量與外界隔離
    • 如果函數需要使用外部全局變量,請使用函數的形參傳參解決
    • 一句話:不用global。學習它就是為了深入理解變量作用域

閉包(重要概念)

  • 自由變量:未在本地作用域中定義的變量。例如定義在內存函數外的外層函數的作用域中的變量
  • 閉包:就是一個概念,出現在嵌套函數中,指的是內層函數引用到了外層函數的自由變量,就形成了閉包。很多語言都有這個概念,最熟悉就是JavaScript
  • 使用global可以解決,但是這使用的是全局變量,而不是閉包
  • 如果要對普通變量的閉包,Python3中可以使用nonlocal

nonlocal關鍵字

  • 使用了nonlocal關鍵字,將變量標記為不在本地作用域定義,而在上級的某一級局部作用域中定義,但不能是全局作用域中定義

默認值的作用域

  • 函數也是對象,python把函數的默認值放在了屬性中,這個屬性就伴隨著這個函數對象的整個生命周期

  • 屬性defaults中使用元組保存所有位置參數默認值,它不會因為在函數體內使用了它而發生改變
  • 屬性kwdefaults中使用字典保存所有keyword-only參數的默認值

  • 使用可變類型作為默認值,就可能修改這個默認值
  • 有時候這個特性是好的,有的時候這種特性是不好的,有副作用

  • 第一種方法
    • 使用影子拷貝創建一個新的對象,永遠不能改變傳入的參數
      def foo(xyz=[], u=‘abc‘, z=123):
      xyz = xyz[:] # 影子拷貝
      xyz.append(1)
      print(xyz)
      foo()
      print(foo.__defaults__)
      foo()
      print(foo.__defaults__)
      foo([10])
      print(foo.__defaults__)
      foo([10,5])
      print(foo.__defaults__)

函數體內,不改變默認值
xyz都是傳入參數或者默認參數的副本,如果就想修改原參數,無能為力

  • 第二種方法
    • 通過值的判斷就可以靈活的選擇創建或者修改傳入對象
    • 這種方式靈活,應用廣泛
    • 很多函數的定義,都可以看到使用None這個不可變的值作為默認參數,可以說這是一種慣用法
def foo(xyz=None, u=‘abc‘, z=123):
    if xyz is None:
        xyz = []
    xyz.append(1)
    print(xyz)
foo()
print(foo.__defaults__)
foo()
print(foo.__defaults__)
foo([10])
print(foo.__defaults__)
foo([10,5])
print(foo.__defaults__)

使用不可變類型默認值
如果使用缺省值None就創建一個列表
如果傳入一個列表,就修改這個列表

變量名解析原則LEGB

  • Local,本地作用域、局部作用域的local命名空間。函數調用時創建,調用結束消亡
  • Enclosing,Python2.2時引入了嵌套函數,實現了閉包,這個就是嵌套函數的外部函數的命名空間
  • Global,全局作用域,即一個模塊的命名空間。模塊被import時創建,解釋器退出時消亡
  • Build-in,內置模塊的命名空間,生命周期從python解釋器啟動時創建到解釋器退出時消亡。例如 print(open),print和open都是內置的變量
  • 所以一個名詞的查找順序就是LEGB

技術分享圖片

函數的銷毀

  • 全局函數銷毀

    • 重新定義同名函數
    • del 語句刪除函數對象
    • 程序結束時
  • 局部函數銷毀
    • 重新在上級作用域定義同名函數
    • del 語句刪除函數名稱,函數對象的引用計數減1
    • 上級作用域銷毀時

遞歸


  • 函數直接或者間接調用自身就是遞歸
  • 遞歸需要有邊界條件、遞歸前進段、遞歸返回段
  • 遞歸一定要有邊界條件
  • 當邊界條件不滿足的時候,遞歸前進
  • 當邊界條件滿足的時候,遞歸返回

  • 遞歸要求

    • 遞歸一定要有推出條件,遞歸調用一定要執行這個退出條件。沒有退出條件的遞歸調用,就是無限調用
    • 遞歸調用的深度不宜過深
    • Python對遞歸調用的深度做了限制以保護解釋器
    • 超過遞歸深度限制,拋出RecursionError maxinum recursion depth exceeded 超出最大深度sys.getrecursionlimit()
  • 遞歸的性能

    • 循環稍微復雜一些,但是只要不是死循環,可以多次叠代直至算出結果
    • 遞歸有深度限制,如果遞歸復雜,函數反復壓棧,占內存很快就溢出了
  • 間接遞歸

    • 通過別的函數調用了函數自身
    • 但是,如果構成了循環遞歸調用是非常危險的,但是往往在代碼復雜的情況下,還是可能發生這種調用。要用代碼的規範來避免這種遞歸調用的發生
  • 總結
    • 遞歸是一種很自然地表達,符合邏輯思維
    • 遞歸相對運行效率低,每一次調用函數都要開辟棧幀
    • 遞歸有深度限制,如果遞歸層次太深,函數反復壓棧,棧內存很快就溢出了
    • 如果是有限次數的遞歸,可以使用遞歸,或者使用循環代替,循環代碼稍微復雜一些,但是只要不是死循環,可以多次叠代直至算出結果
    • 絕大多數遞歸,都可以使用循環實現
    • 即使遞歸代碼很簡潔,但是能不用則不用遞歸

匿名函數


  • 使用Lambda表達式構建匿名函數
  • 格式

    • lambda 參數列表:表達式
  • 使用lambda關鍵字來定義匿名函數
  • 參數列表不需要小括號
  • 冒號是用來分割參數列表和表達式的
  • 不需要使用return,表達式的值,就是匿名函數返回值
  • lambda表達式(匿名函數)只能寫在一行上,被稱為單行函數

  • 用途
    • 在高階函數傳參時,使用lambda表達式,往往能簡化代碼
[x for x in (lambda *args: map(lambda x: x+1, args))(*range(5))] 
[x for x in (lambda *args: map(lambda x: (x+1,args), args))(*range(5))]

Python第四周 學習筆記(1)