1. 程式人生 > >第二模塊:03python學習之函數進階

第二模塊:03python學習之函數進階

算法 內容 全局 原來 遇到 oba trace python學習 多種方法

1.名稱空間

定義:相比上一節的作用域,名稱空間更能解釋。名稱空間又名name space, 顧名思義就是存放名字的地方,存什麽名字呢?舉例說明,若變量x=1,1存放於內存中,那名字x存放在哪裏呢?名稱空間正是存放名字x與1綁定關系的地方。

名稱空間分以下三種:

  • locals:是函數內的名稱空間,包括局部變量和形參,當前函數空間內
  • globals:全局變量,函數定義所在模塊的空間
  • bulildings:內置模塊的名稱空間

針對作用域的查找順序,遵循LEGB的規則

  • L:LOCALS 本地的
  • E:ENCLOSING 相鄰的
  • G:GLOBLS 全局的
  • B:BUILTINGS 內置函數的

函數在調用變量時,遵循從上到下,逐層向下獲取變量,LEGB 代表名字查找順序: locals -> enclosing function -> globals -> __builtins__

看以下代碼

技術分享圖片
level = L0
n = 22


def func():
    level = L1
    n = 33
    print(locals())

    def outer():
        n = 44
        level = L2
        print(locals(),n)

        def inner():
            level = L3
            print(locals(),n) #此外打印的n是多少?
        inner()
    outer()


func()
View Code

2.閉包

關於閉包可以這麽定義:即函數定義和函數表達式位於另一個函數的函數體內(嵌套函數)。而且,這些內部函數可以訪問它們所在的外部函數中聲明的所有局部變量、參數。當其中一個這樣的內部函數在包含它們的外部函數之外被調用時,就會形成閉包。也就是說,內部函數會在外部函數返回後被執行。而當這個內部函數執行時,它仍然必需訪問其外部函數的局部變量、參數以及其他內部函數。這些局部變量、參數和函數聲明(最初時)的值是外部函數返回時的值,但也會受到內部函數的影響。

是不是覺得很麻煩,繞來繞去,先看代碼

def outer():
    name = alex

    def inner():
        
print("在inner裏打印外層函數的變量",name) return inner f = outer() f()

你會發現在與普通的嵌套函數不同的是子函數返回的是函數自己,而不是其他變量什麽的。這個時候可以這麽定義,在外部可以調用內部函數,同時使用內部的值這個就可以稱之為閉包。雖然函數內部的子函數返回子函數自身,在返回的同時,不止是一個函數對象,同時在外面包裹了一層作用域,使得函數在調用的時候優先使用外部的作用域。

3.裝飾器***

對於程序開發來說,我們一般需要遵守‘開放封閉原則’,所謂開放就是說程序有較好的拓展性以及延展性,因為對於一個程序的功能不可能永遠不變,會各種各樣的拓展功能,例如說我們登錄一個程序,以前只有賬戶密碼驗證,後面鑄件出現了QQ驗證,微信認證,微博驗證,這就要求我們的程序可以較好的拓展。而所謂的封閉就是說已經完成的程序代碼最好不動,有可能的是這個操作會發生巨大的工作量,涉及到不同的部門或者不同的流程這樣就會有巨大的變更動作,也有可能是代碼時間已經很久遠,不好重新構建。下面用幾個例子來說明這個事情。

你所在的部門屬於基礎支撐部門,產生核心代碼,其他部門代碼從你這邊調取

你的核心代碼如下

############### 基礎平臺提供的功能如下 ###############
 
def f1():
    print f1
 
def f2():
    print f2
 
def f3():
    print f3
 
def f4():
    print f4
 
############### 業務部門A 調用基礎平臺提供的功能 ###############
 
f1()
f2()
f3()
f4()
 
############### 業務部門B 調用基礎平臺提供的功能 ###############
 
f1()
f2()
f3()
f4()

這個時候老板要求,核心代碼隨意調用有可能泄密,要求調用的時候需要賬戶密碼驗證。對於初學者我們有可能會想到這樣,對每一個被調用的函數進行重編

代碼如下

############### 基礎平臺提供的功能如下 ############### 

def f1():
    # 驗證1
    # 驗證2
    # 驗證3
    print f1

def f2():
    # 驗證1
    # 驗證2
    # 驗證3
    print f2

def f3():
    # 驗證1
    # 驗證2
    # 驗證3
    print f3

def f4():
    # 驗證1
    # 驗證2
    # 驗證3
    print f4

############### 業務部門不變 ############### 
### 業務部門A 調用基礎平臺提供的功能### 

f1()
f2()
f3()
f4()

### 業務部門B 調用基礎平臺提供的功能 ### 

f1()
f2()
f3()
f4()

但是這種情況,我們就發現重復會很多很多,這只是三四個模塊,如果多呢?這樣就太多重復代碼了,有可能說我這樣功能實現了呀,但是作為一個程序員應該是想著用最簡潔的語句完成最多的事情,不過我們有可能在學了一點,想到以下的這種方式。

############### 基礎平臺提供的功能如下 ############### 

def check_login():
    # 驗證1
    # 驗證2
    # 驗證3
    pass


def f1():
    
    check_login()

    print f1

def f2():
    
    check_login()

    print f2

def f3():
    
    check_login()

    print f3

def f4():
    
    check_login()
    
    print f4

專門做一個認證函數,每個基礎模塊做修改調用一次認證函數,認證通過就可以繼續下去,但是我們這樣還是違背了開放封閉原則,對源代碼做了修改,最好的辦法就是認證一次後不需要在此認證。比較高級的代碼就是下面這個,後面會對這個代碼說明。

def w1(func):
    def inner():
        # 驗證1
        # 驗證2
        # 驗證3
        return func()
    return inner
 
@w1
def f1():
    print f1
@w1
def f2():
    print f2
@w1
def f3():
    print f3
@w1
def f4():
    print f4

解釋上面的代碼,對於python程序是由上至下執行,首先返回倆個值

return fi()直接執行f1函數

return inner返回inner函數,並沒有執行

沒錯,從表面上看解釋器僅僅會解釋這兩句代碼,因為函數在沒有被調用之前其內部代碼不會被執行。

從表面上看解釋器著實會執行這兩句,但是 @w1 這一句代碼裏卻有大文章,@函數名 是python的一種語法糖。

如上例@w1內部會執行一下操作:

    • 執行w1函數,並將 @w1 下面的 函數 作為w1函數的參數,即:@w1 等價於 w1(f1)
      所以,內部就會去執行:
      def inner:
      #驗證
      return f1() # func是參數,此時 func 等於 f1
      return inner # 返回的 inner,inner代表的是函數,非執行函數
      其實就是將原來的 f1 函數塞進另外一個函數中
    • 將執行完的 w1 函數返回值賦值給@w1下面的函數的函數名
      w1函數的返回值是:
      def inner:
      #驗證
      return 原來f1() # 此處的 f1 表示原來的f1函數
      然後,將此返回值再重新賦值給 f1,即:
      新f1 = def inner:
      #驗證
      return 原來f1()
      所以,以後業務部門想要執行 f1 函數時,就會執行 新f1 函數,在 新f1 函數內部先執行驗證,再執行原來的f1函數,然後將 原來f1 函數的返回值 返回給了業務調用者。
      如此一來, 即執行了驗證的功能,又執行了原來f1函數的內容,並將原f1函數返回值 返回給業務調用著

所以我們可以這麽理解,裝飾器只是作為一個語法糖包含在函數外面,將函數作為參數傳遞給認證函數執行,對於調用者而言其實是執行,調用的f1(),其實是調用的inner函數,然後做認證,執行真正f1()函數

對於裝飾器還有其他

帶參數

#單參數
def w1(func):
    def inner(arg):
        # 驗證1
        # 驗證2
        # 驗證3
        return func(arg)
    return inner

@w1
def f1(arg):
    print f1

一個參數
#倆個參數
def w1(func):
    def inner(arg1,arg2):
        # 驗證1
        # 驗證2
        # 驗證3
        return func(arg1,arg2)
    return inner

@w1
def f1(arg1,arg2):
    print f1

兩個參數

#多個參數
def w1(func):
    def inner(*args,**kwargs):
        # 驗證1
        # 驗證2
        # 驗證3
        return func(*args,**kwargs)
    return inner
 
@w1
def f1(arg1,arg2,arg3):
    print f1

雙裝飾器

def w1(func):
    def inner(*args,**kwargs):
        # 驗證1
        # 驗證2
        # 驗證3
        return func(*args,**kwargs)
    return inner
 
def w2(func):
    def inner(*args,**kwargs):
        # 驗證1
        # 驗證2
        # 驗證3
        return func(*args,**kwargs)
    return inner
 
 
@w1
@w2
def f1(arg1,arg2,arg3):
    print f1

其他類型的需要後面再添加

4.列表生成器

現在有個需求,看列表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],要求你把列表裏的每個值加1,你怎麽實現?你可能會想到2種方式

第一種

a = [1,3,4,6,7,7,8,9,11]
b = []
for i in a:b.append(i+1)
    a = b

第二種

b = []
>>> for i in a:b.append(i+1)
>>> a = b

稍微裝逼一點的

a =[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a = map(lambda x:x+1, a)
for i in a:print(i)

最裝逼的就是下面這種了

a = [i+1 for i in range(10)]

直接一行代碼搞定,減少代碼量,這種就叫做列表生成式

5.生成器

通過列表生成式,我們可以直接創建一個列表。但是,受到內存限制,列表容量肯定是有限的。而且,創建一個包含100萬個元素的列表,不僅占用很大的存儲空間,如果我們僅僅需要訪問前面幾個元素,那後面絕大多數元素占用的空間都白白浪費了。

所以,如果列表元素可以按照某種算法推算出來,那我們是否可以在循環的過程中不斷推算出後續的元素呢?這樣就不必創建完整的list,從而節省大量的空間。在Python中,這種一邊循環一邊計算的機制,稱為生成器:generator。

要創建一個generator,有很多種方法。

第一種方法很簡單,只要把一個列表生成式的[]改成(),就創建了一個generator:

第二種方法,yield 把函數變成生成器

5.1看以下代碼

list1 = [i for i in range(100)]
list1 =[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27
, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 
55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86,
 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]

我們很容易就用列表生成式生成一個100元素的列表,那麽如果要生成一個一個出現的額生成式,只需要將[]變為()就可以了,看以下代碼

>>> li = (i for i in range(100))
>>> li
<generator object <genexpr> at 0x0220C6E8>

對於一個生成式獲取他下一個元素就有倆種方式next(li) 和 li.__next__(),就可以輕松獲取到下一個元素

不過需要註意的是,在調用next()的函數的時候,在最後一個位置會拋出一個錯誤,必須記住生成器只能往前,不能後退,也不可以切片,走到最後直接報錯

>>> next(li)
8
>>> next(li)
9
>>> next(li)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

#每次調用next(g)就計算出g的下一個元素的值,直到計算到最後一個元素,沒有更多的元素時,拋出StopIteration的錯誤。

range對於python2與3的區別

技術分享圖片

在python3中,range和文件的打開也是生成器

5.2用yield語法生成一個生成器

如果一個函數定義中包含yield關鍵字,那麽這個函數就不再是一個普通函數,而是一個generator

def fib(max):
    n,a,b = 0,0,1

    while n < max:
        #print(b)
        yield  b
        a,b = b,a+b

        n += 1

    return done



>>> f = fib(6)
>>> f
<generator object fib at 0x104feaaa0>

yield與return的區別

return返回並中止function

yield返回數據,並凍結當前進程

next 喚醒凍結的函數執行過程,繼續執行直到下一個yield

return在生成器中,代表生成器的中止,會直接報錯

5.3生成器send

例子:執行到yield時,gen函數作用暫時保存,返回i的值;temp接收下次c.send("python"),send發送過來的值,c.next()等價c.send(None)

def fib(max):
    a, b, n = 0, 1, 0
    while n < max:
        send_msg = yield b
        print(send_msg)
        a, b = b, a+b
        n += 1

f = fib(10)
print(next(f))
f.send(寄個快遞)
print(next(f))

技術分享圖片

f.send(None)    #第一次相當於f.__next__()

總結:

生成器是這樣一個函數,它記住上一次返回時在函數體中的位置。對生成器函數的第二次(或第 n 次)調用跳轉至該函數中間,而上次調用的所有局部變量都保持不變。

生成器不僅“記住”了它數據狀態;生成器還“記住”了它在流控制構造(在命令式編程中,這種構造不只是數據值)中的位置。

生成器的特點:

  1. 節約內存
  2. 叠代到下一次的調用時,所使用的參數都是第一次所保留下的,即是說,在整個所有函數調用的參數都是第一次所調用時保留的,而不是新創建的

6.叠代器

我們已經知道,可以直接作用於for循環的數據類型有以下幾種:

  • 一類是集合數據類型,如listtupledictsetstr等;
  • 一類是generator,包括生成器和帶yield的generator function。

這些可以直接作用於for循環的對象統稱為可叠代對象:Iterable

可以使用isinstance()判斷一個對象是否是Iterable對象:

>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance(abc, Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False

而生成器不但可以作用於for循環,還可以被next()函數不斷調用並返回下一個值,直到最後拋出StopIteration錯誤表示無法繼續返回下一個值了。

*可以被next()函數調用並不斷返回下一個值的對象稱為叠代器:Iterator。

可以使用isinstance()判斷一個對象是否是Iterator對象:

>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance(abc, Iterator)
False

生成器都是Iterator對象,但listdictstr雖然是Iterable,卻不是Iterator

listdictstrIterable變成Iterator可以使用iter()函數:

>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter(abc), Iterator)
True

技術分享圖片

技術分享圖片

總結

凡是可作用於for循環的對象都是Iterable類型;

凡是可作用於next()函數的對象都是Iterator類型,它們表示一個惰性計算的序列;

集合數據類型如listdictstr等是Iterable但不是Iterator,不過可以通過iter()函數獲得一個Iterator對象。

Python3的for循環本質上就是通過不斷調用next()函數實現的,例如:

for x in [1, 2, 3, 4, 5]:
    pass

實際上完全等價於:

# 首先獲得Iterator對象:
it = iter([1, 2, 3, 4, 5])
# 循環:
while True:
    try:
        # 獲得下一個值:
        x = next(it)
    except StopIteration:
        # 遇到StopIteration就退出循環
        break

第二模塊:03python學習之函數進階