1. 程式人生 > >Python自動化運維之函數進階

Python自動化運維之函數進階

高階函數 閉包 生成器 叠代器

1、函數嵌套
函數的嵌套定義:在一個函數的內部,又定義了另外一個函數
函數的嵌套調用:在調用一個函數的過程中,又調用了其他函數
代碼:

>>> def f1():
...     def f2():
...         print(‘from f2‘)
...         def f3():
...             print(‘from f3‘)
...         f3()
...     f2()
...

執行結果:

>>> f1()
from f2
from f3

2、命名空間與作用域:存放名字的地方,確切的是存放的名字與變量值的綁定的關系的空間

名稱空間分類:內置名稱空間、全局名稱空間和局部名稱空間
內置名稱空間:Python解釋器啟動時產生一些內置名字
全局名稱空間:在執行文件時產生的,存放在文件級別(流程控制語句定義、未縮進定義的)定義的名字
局部名稱函數:
加載順序:內置---->全局---->局部
名稱查找順序:局部---->全局---->內置
作用域:作用的範圍
作用域分類:全局作用域和局部作用域
全局作用域:全局存活,全局有效。查看函數globals(),顯示字典類型
局部作用域:臨時存活,局部生效。查看函數locals(),顯示字典類型
關鍵字:global nonlocal
局部修改全局,對於不可變類型需要使用global,可變類型無需使用global
局部修改局部,對於不可變類型需要使用nonlocal,可變類型無需使用nonlocal
註意:盡量避免使用局部修改全局
重點:作用域關系,在函數定義時,函數中名稱查找就已經固定和調用位置無關。在調用函數時,必須回到函數原來定義的位置去找作用域關系

x = 1
def f1():
    def f2():
        print(x) # x 為全局作用域
    return f2()
def foo():
    x = 100
    f1()
foo()
x = 10
foo()

3、閉包函數
閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。

閉包可以形象的把它理解為一個封閉的包裹,這個包裹就是一個函數,當然還有函數內部對應的邏輯,包裹裏面的東西就是自由變量,自由變量可以在隨著包裹到處遊蕩。當然還得有個前提,這個包裹是被創建出來的。
在Python中可以這樣理解,一個閉包就是我調用了一個函數A,這個函數A返回了一個函數B給我。這個返回的函數B就叫做閉包。我在調用函數A的時候傳遞的參數就是自由變量。

1. 定義
python中的閉包從表現形式上定義(解釋)為:如果在一個內部函數裏,對在外部作用域(但不是在全局作用域)的變量進行引用,那麽內部函數就被認為是閉包(closure).這個定義是相對直白的,好理解的,不像其他定義那樣學究味道十足(那些學究味道重的解釋,在對一個名詞的解釋過程中又充滿了一堆讓人抓狂的其他陌生名詞,不適合初學者)。下面舉一個簡單的例子來說明。

>>> def addx(x):
...     def adder(y): return x + y
...     return adder
...
>>> c = addx(8)
>>> type(c)
<class ‘function‘> 
>>> c.__name__
‘adder‘ 
>>> c(10)
18

結合這段簡單的代碼和定義來說明閉包:如果在一個內部函數裏:adder(y)就是這個內部函數,對在外部作用域(但不是在全局作用域)的變量進行引用:x就是被引用的變量,x在外部作用域addx裏面,但不在全局作用域裏,則這個內部函數adder就是一個閉包。
閉包=函數塊+定義函數時的環境,adder就是函數塊,x就是環境,當然這個環境可以有很多,不止一個簡單的x。所以,如果要用一句話說明白閉包函數,那就是:函數內在包含子函數,並最終return子函數。
2. 使用閉包註意事項
2.1 閉包中是不能修改外部作用域的局部變量的

>>> def foo():
...     m = 0
...     def foo1():
...         m = 1
...         print(m)
...     print(m)
...     foo1()
...     print(m)
...
>>> foo()
0
1
0

從執行結果可以看出,雖然在閉包裏面也定義了一個變量m,但是其不會改變外部函數中的局部變量m。

2.2 以下這段代碼是在python中使用閉包時一段經典的錯誤代碼

>>> def foo():
...     a = 1
...     def bar():
...         a = a + 1
...         return a
...     return bar
...

這段程序的本意是要通過在每次調用閉包函數時都對變量a進行遞增的操作。但在實際使用時

>>> c=foo()
>>> print(c())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in bar
UnboundLocalError: local variable ‘a‘ referenced before assignment

這是因為在執行代碼c=foo()時,python會導入全部的閉包函數體bar()來分析其的局部變量,python規則指定所有在賦值語句左面的變量都是局部變量,則在閉包bar()中,變量a在賦值符號"="的左面,被python認為是bar()中的局部變量。再接下來執行print c()時,程序運行至a = a + 1時,因為先前已經把a歸為bar()中的局部變量,所以python會在bar()中去找在賦值語句右面的a的值,結果找不到,就會報錯。解決的方法很簡單

def foo():  
    a = [1]  
    def bar():  
        a[0] = a[0] + 1  
        return a[0]  
    return bar

只要將a設定為一個容器就可以了。這樣使用起來多少有點不爽,所以在python3以後,在a = a + 1 之前,使用語句nonloacal a就可以了,該語句顯式的指定a不是閉包的局部變量。


4、裝飾器
1. 裝飾器其實就是一個以函數作為參數並返回一個替換函數的可執行函數。讓我們從簡單的開始,直到能寫出實用的裝飾器。
封閉原則:對擴展是開放的,對修改是封閉的。

>>> def outer(some_func):
...     def inner():
...         print("hello world!")
...         ret = some_func() # 1
...         return ret + 1
...     return inner
...
>>> def foo():
...     return 1
...
>>> decorated = outer(foo) #2
>>> decorated()
hello world!

請仔細看這個裝飾器示例。首先,定義了一個帶單個參數some_func的名為outer的函數。然後在outer內部定義了一個內嵌函數inner.inner 函數將打印一行字符串然後調用some_func,並在 #1 處獲取其返回值。在每次 outer 被調用時,some_func 的值可能都會不同,但不論 some_func 是什麽函數,都將調用它。最後,inner 返回 some_func() 的返回值加 1。在 #2 處可以看到,當調用賦值給 decorated 的返回函數時,得到的是一行文本輸出和返回值 2,而非期望的調用 foo 的返回值 1。

我們可以說變量decorated是foo的裝飾版——即foo加上一些東西。事實上,如果寫了一個實用的裝飾器,可能會想用裝飾版來代替foo,這樣就總能得到“附帶其他東西”的foo版本。用不著學習任何新的語法,通過將包含函數的變量重新賦值就能輕松做到這一點:

>>> foo = outer(foo)
>>> foo
<function outer.<locals>.inner at 0x000000F47F5CBA60>

現在任意調用foo()都不會得到原來的foo,而是新的裝飾器版!明白了嗎?來寫一個更實用的裝飾器。

2. [email protected]

import time
def timemer(func):
    def wrapper(*args,**kwargs):
        start = time.time()
        RETURN=func()
        stop = time.time()
        print("run time is: %s" % (stop-start))
        return RETURN
    return wrapper
如果定義了一個函數,可以用 @ 符號來裝飾函數,如下:
@timemer
def index():
    time.sleep(2)
    print("welcome to index")
index()

值得註意的是,這種方式和簡單的使用index函數的返回值來替換原始變量的做法沒有什麽不同——Python只是添加了一些語法來使之看起來更加明確。
使用裝飾器很簡單!雖說寫類似staticmethod或者classmethod的實用裝飾器比較難,[email protected] 即可!
3. *args和*kwargs
上面我們寫了一個實用的裝飾器,但它是硬編碼的,只適用於特定類型的函數——帶有兩個參數的函數。內部函數checker接收兩個參數,然後繼續將參數傳給閉包中的函數。如果我們想要一個能適用任何函數的裝飾器呢?讓我們來實現一個為每次被裝飾函數的調用添加一個計數器的裝飾器,但不改變被裝飾函數。這意味著這個裝飾器必須接收它所裝飾的任何函數的調用信息,並且在調用這些函數時將傳遞給該裝飾器的任何參數都傳遞給它們。

import time
def timemer(func):
    def wrapper(*args,**kwargs):
        start = time.time()
        RETURN=func(*args,**kwargs)
        stop = time.time()
        print("run time is: %s" % (stop-start))
        return RETURN
    return wrapper

如果定義了一個函數,可以用 @ 符號來裝飾函數,如下:

@timemer
def index(name):
    time.sleep(2)
    print("welcome %s to index" %name)
index("name")

同時使用*args和*kwargs使得裝飾器更加的通用

4. 無參裝飾器

current_usr={"user":None}
def auth(func):
    info = {
        "xuanwei": {"password": "123123", "number": 0},
        "linglong": {"password": "123123", "number": 0}
    }
    def wrapper(*args,**kwargs):
        if current_usr["user"]:
            return func(*args, **kwargs)
        else:
            while True:
                username = input("請輸入用戶名: ")
                if username in info:
                    password = input("請輸入密碼: ")
                    if info[username]["number"] > 1:
                        print("%s 用戶已收鎖定" % username)
                        exit()
                    if password == info[username]["password"]:
                        current_usr["user"] = username
                        RETURN = func(*args, **kwargs)
                        return RETURN
                    else:
                        print("用戶密碼錯誤")
                        info[username]["number"] += 1
                else:
                    print("%s 用戶不存在" % username)
    return wrapper
@auth
def index1():
    print("welcome to index1")
    return 123
def index2():
    print("welcome to index1")
    return 123
index1()
index2()

5. 有參裝飾器

def auth(auth_type = "dict"):
    def deco(func):
        current_usr = {"user": None}
        def wrapper(*args,**kwargs):
            if current_usr["user"]:
                return func(*args, **kwargs)
            else:
                if auth_type == "file":
                    with open("db.txt", "r", encoding="utf-8") as read_f:
                        info = eval(read_f.read())
                else :
                    info = {
                        "xuanwei": {"password": "123123", "number": 0},
                    }
                while True:
                    username = input("請輸入用戶名: ")
                    if username in info:
                        password = input("請輸入密碼: ")
                        if info[username]["number"] > 1:
                            print("%s 用戶已收鎖定" % username)
                            exit()
                        if password == info[username]["password"]:
                            current_usr["user"] = username
                            RETURN = func(*args, **kwargs)
                            return RETURN
                        else:
                            print("用戶密碼錯誤")
                            info[username]["number"] += 1
                    else:
                        print("%s 用戶不存在" % username)
        return wrapper
    return deco
@auth()
def index():
    print("welcome to index")
    return 123
index()

5、叠代器
叠代:是一個重復的過程,每一次重復,都是基於上一次的結果而來
什麽是叠代器對象
有__iter__和__next__方法,並且執行__iter__得到仍是叠代器本身
叠代器對象的優點
1. 提供了一種統一(不依賴索引)的叠代方式
2. 叠代器本身,比起其他數據類型更少內存,next()在內存中只有1個值
叠代器對象的缺點
1. 取值不靈活。只能往後走,不能回退,沒有索引取值靈活
2. 無法預知取值結束,即無法預知長度
判斷可叠代器對象與叠代器對象
from collections import Iterator
判斷是否是可叠代器對象
print(isinstance(對象,Iterator))

6、生成器:在函數內存包含yield關鍵字,那麽該函數執行的結果是生成器,並且生成器就是叠代器
關鍵字yield的功能:
(1) 包函數的結果做出叠代器,以一種優雅的方式封裝好__iter__,__next__方法
(2) 函數暫停與再繼續運行的狀態是有yield保存的
return 和 yield的區別:
(1) 功能相同:都能返回值
(2) 不同:return只能執行一次

本文出自 “炫維” 博客,請務必保留此出處http://xuanwei.blog.51cto.com/11489734/1951593

Python自動化運維之函數進階