【函數】02、函數進階


一、函數的返回值

In [4]: def add(x, y):
   ...:     return x+y
   ...: 

In [5]: add(1, 2)
Out[5]: 3


In [8]: def add(x, y):
   ...:     return x+y
   ...:     print(x+y)
   ...:     

In [9]: add(1, 2)
Out[9]: 3

In [16]: def add(x, y):
    ...:     return "abc", x+y
    ...:     return x+y
    ...:     

In [17]: add(1, 2)
Out[17]: ('abc', 3)

  
In [18]: def add(x, y):
    ...:     return
    ...: 
    ...:     

In [19]: add(1, 2)

In [20]: a = add(1, 2)

In [21]: a

In [22]: type(a)
Out[22]: NoneType


In [25]: def add(x, y):
    ...:     return None
    ...: 

In [26]: add(1, 2)

In [27]: type(add(1, 2))
Out[27]: NoneType


關鍵字:return

     return只能出現在函數中,可以返回任何對象,可以作爲元祖變相的返回多個值

     所有函數都有返回值,如果沒定義return則默認返回值None

     return語句除了返回值之外還會結束函數

    1個函數可以有多個return語句,但只會執行一個


二、作用域

1、函數嵌套

  函數可以嵌套定義

In [28]: def outter():
    ...:     def inner():
    ...:         print('inner')
    ...:     print('outter')
    ...:     inner()
    ...:     

In [29]: outter()
outter
inner


2、作用域

   作用域是一個變量的可見範圍

   函數內部是一個局部作用域,外面叫全局作用域

   不同作用域變量不可見,但是上級作用域的變量對下級只讀可見

In [32]: x = 1

In [33]: def inc():
    ...:     x += 1   # x此時是局部變量,不能直接使用全局作用域的變量
    ...:     

In [34]: inc()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-34-ae671e6b904f> in <module>()
----> 1 inc()

<ipython-input-33-661b9217054c> in inc()
      1 def inc():
----> 2     x += 1
      3 

UnboundLocalError: local variable 'x' referenced before assignment


In [40]: x = 1

In [41]: def fn():
    ...:     print(x)   
    ...:     

In [42]: fn()    # 爲什麼這裏能打印出來,不拋出錯誤呢
1


變量的作用域爲定義此變量的作用域:

In [43]: def fn():
    ...:     name = "xxj"
    ...:     print(name)
    ...:     

In [44]: fn()
xxj

In [45]: name
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-45-18697449d7c4> in <module>()
----> 1 name

NameError: name 'name' is not defined


上級作用域對下級作用域是隻讀可見的

In [46]: def fn():
    ...:     xx = 1
    ...:     print(xx)
    ...:     def inner():
    ...:         print(xx)
    ...:     inner()
    ...:     

In [47]: fn()
1
1


In [48]: def fn():
    ...:     xx = 1
    ...:     print(xx)
    ...:     def inner():
    ...:         xx = 2
    ...:         print(xx)
    ...:     inner()
    ...:     print(xx)
    ...:     

In [49]: fn()
1
2
1


global關鍵字能且只能引用全局作用域中的變量;引用後的變量能讀寫

如果全局作用域有此變量名,則引用,

沒有則需要定義,定義後此作用域和全局作用域中可見;不定義則會報錯,

In [53]: xx = 1

In [54]: def fn():
    ...:     global xx    # global關鍵字能顯式的提升1個變量的作用域
    ...:     xx += 1
    ...:     

In [55]: fn()

In [56]: xx
Out[56]: 2

In [57]: fn()

In [58]: xx
Out[58]: 3



In [68]: xxj
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-68-bc382da45e82> in <module>()
----> 1 xxj

NameError: name 'xxj' is not defined

In [69]: def fn():
    ...:     global xxj  # 如果此變量沒有定義,則此提升變量作用域沒有意義 
    ...:     
    ...:     

In [70]: fn()

In [71]: xxj
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-71-bc382da45e82> in <module>()
----> 1 xxj

NameError: name 'xxj' is not defined


  除非你清楚的知道global會帶來什麼,並且明確的知道沒有global不行的話,否則不要用global;就是不建議使用global關鍵字


3、默認參數的作用域

函數也是對象,參數是函數對象的屬性,所以函數參數的作用域伴隨函數整個生命週期

In [92]: def fn(xy=[]):
    ...:     xy.append(1)
    ...:     print(xy)
    ...:     

In [93]: fn()
[1]

In [94]: fn()
[1, 1]

In [95]: fn()
[1, 1, 1]

In [96]: fn.__defaults__     # 函數默認參數的值保存在函數__defaults__屬性中
Out[96]: ([1, 1, 1],)

In [98]: fn()
[1, 1, 1, 1]

In [100]: fn.__defaults__
Out[100]: ([1, 1, 1, 1],)

  當使用可變類型數據作爲默認參數默認值時,需要特別注意。

In [105]: def fn(x=0, y=0):
     ...:     x = 1  # 賦值即定義
     ...:     y = 2
     ...:     print(x)
     ...:     print(y)
     ...:     

In [106]: fn.__defaults__
Out[106]: (0, 0)

In [107]: fn()
1
2

In [108]: fn.__defaults__
Out[108]: (0, 0)

解決方案:

  使用不可變類型作爲默認值

  函數體內不改變默認值

In [109]: def fn(lst=None):     # 如果傳入的參數是非None,那麼改變了傳入參數;
     ...:     if lst is None:
     ...:         lst = []
              else:
                  lst = lst[:]
     ...:     lst.append(3)
     ...:     print(lst)
     ...:     

In [110]: fn.__defaults__
Out[110]: (None,)

In [111]: fn()
[3]

In [112]: fn.__defaults__
Out[112]: (None,)


In [113]: def fn(lst=[]):
     ...:     lst = lst[:]    # 淺拷貝
     ...:     lst.append(2)   # 無論如何不修改傳入參數
     ...:     print(lst)
     ...:     

In [114]: fn.__defaults__
Out[114]: ([],)

In [115]: fn()
[2]

In [116]: fn.__defaults__
Out[116]: ([],)

  通常如果使用一個可變參數作爲默認參數時,會使用None來代替


4、命名空間與LEGB

 1)命名空間 

       理解Python的LEGB原則是理解Python命名空間的關鍵,而理解Python的命名空間又是理解Python中許多語法規定的關鍵。所以,Python的LEGB原則就成爲Python中一個非常核心的內容

白話一點講:命名空間是對變量名的分組劃分
       不同組的相同名稱的變量視爲兩個獨立的變量,因此隸屬於不同分組(即命名空間)的變量名可以重複。
命名空間可以存在多個,使用命名空間,表示在該命名空間中查找當前名稱。


       命名空間表示變量的可見範圍,一個變量名可以定義在多個不同的命名空間,相互之間並不衝突,但同一個命名空間中不能有兩個相同的變量名。

比如:兩個叫“張三”的學生可以同時存在於班級A和班級B中,如果兩個張三都是一個班級,那麼帶來的麻煩複雜很多了,在Python中你不能這麼幹。

      在Python中用字典來表示一個命名空間,命名空間中保存了變量(名字)和對象的映射關係,在Python中命名空間出現在哪些地方呢?有函數範圍內的命名空間(local),有模塊範圍內的命名空間(global),有python內建的命名空間(built-in),還有類對象的所有屬性組成的命名空間

Python一切皆對象,所以在Python中變量名是字符串對象

例如:

1
In [25]: a=10

      表示建立字符串對象aNumber對象10之間的對應關係。由於這是一種映射關係,所以,可以使用鍵-值的形式來表示,即{name : object}
前面已經說過,命名空間是對變量名的分組劃分,所以,Python的命名空間就是對許多鍵-值對的分組劃分,即,鍵值對的集合,因此:

Python的命名空間是一個字典,字典內保存了變量名稱與對象之間的映射關係


 2)命名空間的生命週期

       所有的命名空間都是有生命週期的,對於python內建的命名空間,python解析器啓動時創建,一直保留直至直python解析器退出時才消亡。而對於函數的local命名空間是在函數每次被調用的時候創建,調用完成函數返回時消亡,而對於模塊的global命名空間是在該模塊被import的時候創建,解析器退出時消亡。


 3)作用域

  一個作用域是指一段程序的正文區域,可以是一個函數或一段代碼。

 一個變量的作用域是指該變量的有效範圍。Python的作用域是靜態作用域,因爲它是由代碼中得位置決定的,而命名空間就是作用域的動態表現。

函數定義了本地作用域,而模塊定義了全局作用域:

       每個模塊都是一個全局作用域,因此,全局作用域的範圍僅限於單個程序文件

       每次對函數的調用都會創建一個新的本地作用域,賦值的變量除非聲明爲全局變量,否則均爲本地變量

       所有的變量名都可以歸納爲本地,全局或內置的(由__builtin__模塊提供)

      

 4)LEGB原則

LEGB含義解釋:
       L-Local(function);函數內的名字空間
       E-Enclosing function locals;外部嵌套函數的名字空間(例如closure)
       G-Global(module);函數定義所在模塊(文件)的名字空間
       B-Builtin(Python);Python內置模塊的名字空間,
builtin作用域,對應builtin命名空間,python內部定義的最頂層的作用域,在這個作用域裏面定義了各種內建函數:open、range、xrange、list等等      

 前面講到,Python的命名空間是一個字典,字典內保存了變量名與對象之間的映射關係

因此,查找變量名就是在命名空間字典中查找鍵-值對
Python有多個命名空間,因此,需要有規則來規定,按照怎樣的順序來查找命名空間LEGB就是用來規定命名空間查找順序的規則。

  LEGB規定了查找一個名稱的順序爲:local-->enclosing function locals-->global-->builtin


三、閉包

當一個函數結束了,函數的內部部分變量引用還存在,這就叫閉包

外層函數主要爲內層函數提供環境

定義在外層函數內,卻由內層函數引用的變量,在外層函數返回時,如果外層函數返回的值是內層函數,再次調用內層函數時,會記憶下內層函數調用的外層函數的變量。


python的閉包可以使用可變容器實現,這也是python2唯一的方式

In [91]: def counter():
    ...:     c = [0]
    ...:     def inc():
    ...:         c[0] += 1
    ...:         return c
    ...:     return inc
    ...: 

In [92]: type(counter)
Out[92]: function

In [93]: type(counter())
Out[93]: function

In [94]: type(counter()())
Out[94]: list

In [95]: f = counter()

In [96]: f()
Out[96]: [1]

In [97]: f()
Out[97]: [2]

In [98]: f()
Out[98]: [3]


nonlocal關鍵字:

  nonlocal關鍵字能且只能引用外部函數作用域中已存在的變量(不能在自己的作用域中定義);引用後的變量能讀寫

In [102]: def counter():
     ...:     x = 0
     ...:     def inc():
     ...:         nonlocal x
     ...:         x += 1
     ...:         return x
     ...:     return inc
     ...: 

In [103]: f = counter()

In [104]: f()
Out[104]: 1

In [105]: f()
Out[105]: 2

In [106]: f()
Out[106]: 3


四、遞歸函數

    函數體內調用自身的函數       

    遞歸函數需要有合適的退出條件,否則就成了死循環; 遞歸需要邊界條件,遞歸前進段和遞歸返回段 

    在python中爲了保護解釋器,遞歸深度最大爲1000

    python中應儘量避免遞歸,效率低(能轉化爲迭代儘量轉化爲迭代) 

In [2]: def fib(n):
   ...:     if n == 0:
   ...:         return 1
   ...:     if n == 1:
   ...:         return 1
   ...:     return fib(n-1) + fib(n-2)
   ...: 

In [3]: fib(5)
Out[3]: 8

In [4]: fib(6)
Out[4]: 13



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章