豬行天下之Python基礎——5.2 函式(下)

coder-pig發表於2019-04-06

內容簡述:

  • 1、迭代器
  • 2、生成器
  • 3、裝飾器

1、迭代器

迭代器是Python提供的用於訪問集合,是一種可以記住遍歷位置的物件,會從第一個元素開始訪問直到結束。可以通過內建的iter()函式對應的迭代器物件,然後直接迴圈遍歷這個迭代器物件;或者通過另外一個內建的next()函式,返回容器的下一個元素,不過如果超過結尾會報StopIteration異常,使用程式碼示例如下:

import sys

a = [12345]
it1 = iter(a)

# 直接遍歷迭代器物件
for x in it1:
    print(x, end='\t')
else:
    print()

# 每呼叫一次next向後訪問一個元素,超過元素會報StopIteration異常
it2 = iter(a)
print(next(it2), end='\t')
print(next(it2), end='\t')
print(next(it2), end='\t')
print(next(it2), end='\t')
print(next(it2), end='\t')
print()

# 帶異常捕獲方法
it3 = iter(a)
while True:
    try:
        print(next(it3), end='\t')
    except StopIteration:
        sys.exit()
複製程式碼

執行結果如下

1   2   3   4   5 
1   2   3   4   5 
1   2   3   4   5
複製程式碼

另外有一點要進行區分:iterableiterator,前者只實現了iter函式,而後者同時實現了iter和next函式。比如List只是一個iterable而不是iterator,所以只支援iter(),不支援next(),需要呼叫iter()函式獲得一個iterator物件。如果呼叫下type(it1),會發現資料型別是:<class 'list_iterator'>,這是Python為list提供的預設迭代器物件。我們也可以自己來實現一個iterator,程式碼示例如下:

class MyIterator():
    def __init__(self, names):
        self.names = names
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index >= len(self.names):
            raise StopIteration("已到達末尾")
        else:
            self.index += 1
            return self.names[self.index - 1]

iterator = MyIterator([12345])
for i in iterator:
    print(i, end='\t')
複製程式碼

執行結果如下

1   2   3   4   5
複製程式碼

2、生成器

叫「生成器函式」會更貼切一些,一種特別的函式,用yield關鍵字 來返回一個生成器物件本質上還是迭代器,只是更加簡潔yield對應的值在函式呼叫時候不會立即返回,只有去呼叫next()函式的時候才會返回,而使用for xxx in xxx的時候其實呼叫的還是next()函式,最簡單的生成器程式碼示例如下:

def func(n):
    yield n * n

if __name__ == '__main__':
    print(func(10))
    print(next(func(10)))
    for i in func(10):
        print(i)
複製程式碼

執行結果如下

<generator object func at 0x0000015D06436308>
100
100
複製程式碼

可以看到返回的物件型別是generator相比起迭代器,生成器顯得更加優雅簡潔,比如最經典的實現斐波那契數列的例子:

def func(n):
    a, b = 01
    while n > 0:
        n -= 1
        yield b
        a, b = b, a + b

for i in func(10):
    print(i, end="\t")
複製程式碼

執行結果如下

1 1 2 3 5 8 13 21 34 55
複製程式碼

3、裝飾器

本質上也是「函式」,作用是:幫助其他函式在不改動程式碼的情況下增加額外的功能,裝飾器函式的返回值也是一個函式。

① 一步步瞭解裝飾器

裝飾器對於很多初學者來說有點難以理解,舉個簡單的奶茶例子慢慢引入吧(因為手邊剛好有一杯一點點奶茶,另外函式名不要用中文,這裡用中文只是想讓讀者更容易理解,還有命名習慣:類名駝峰命名函式名下劃線分割命名!)

import time

def 波霸奶茶():
    time.sleep(1)
    print("製作一杯波霸奶茶")

if __name__ == '__main__':
    波霸奶茶()
複製程式碼

執行結果如下

製作一杯波霸奶茶
複製程式碼

現在筆者想知道製作一杯波霸奶茶花費的時間,可以改動下波霸奶茶這個函式:

def 波霸奶茶():
    start = time.clock()
    time.sleep(2)
    print("製作一杯波霸奶茶")
    end = time.clock()
    print("耗時"end - start)

if __name__ == '__main__':
    波霸奶茶()
複製程式碼

執行結果如下

製作一杯波霸奶茶
耗時 1.004335880279541
複製程式碼

這個時候問題來了,除了波霸奶茶還有四季奶綠這款奶茶:

def 四季奶綠():
    time.sleep(2)
    print("製作一杯四季奶綠")
複製程式碼

而我也想知道四季奶綠的製作時間,需要把統計時間那部分程式碼貼上複製一下,有點繁瑣,如果還有一款格雷三兄弟,也是要複製下,好繁瑣啊。有沒有什麼方法能簡化這個過程呢?答案肯定是有的:「Python支援函式作為引數」,我們可以把計時部分剝離成一個單獨的函式,然後把需要檢視製作時間的奶茶函式傳入即可,所以有了下面這樣的程式碼:

def 波霸奶茶():
    time.sleep(1)
    print("製作一杯波霸奶茶")

def 四季奶綠():
    time.sleep(2)
print("製作一杯四季奶綠")

def 計時(func):
    start = time.time()
    func()
    end = time.time()
print("耗時", end - start)

if __name__ == '__main__':
    計時(波霸奶茶)
    計時(四季奶綠)
複製程式碼

執行結果如下

製作一杯波霸奶茶
耗時 1.0002198219299316
製作一杯四季奶綠
耗時 2.0016701221466064
複製程式碼

可以,這個時候問題又來了,上面的程式碼意味著:所有呼叫到製作奶茶方法的地方都要改成計時(奶茶),如果奶茶有N多種的話,每個呼叫製作奶茶的函式都要改成這樣的形式,有沒有更簡單的方法啊?答案肯定是有的,Python支援返回一個函式。我們要做的是通過一個內嵌的包裝函式為傳入的函式附加功能然後返回這個函式,具體的程式碼如下:

def 計時(func):
    def decorator():
        start = time.time()
        func()
        end = time.time()
        print("耗時"end - start)
return decorator

if __name__ == '__main__':
    波霸奶茶 = 計時(波霸奶茶)
    波霸奶茶()
    四季奶綠 = 計時(四季奶綠)
    四季奶綠()
複製程式碼

執行結果如下

製作一杯波霸奶茶
耗時 1.0033059120178223
製作一杯四季奶綠
耗時 2.0044848918914795
複製程式碼

可以,沒毛病,而且只有在我們需要查詢某種奶茶的製作時間時才需要這樣寫,平常的函式還是正常使用。嗯,你以為到這裡就完了,對於裝飾器函式,Python還為我們提供了一枚語法糖,語法糖可以理解成一種便捷的寫法吧,在Python中通過一個@就可以簡化我們上面的程式碼,具體程式碼如下:

def 計時(func):
    def decorator():
        start = time.time()
        func()
        end = time.time()
        print("耗時", end - start)
return decorator

@計時
def 波霸奶茶():
    time.sleep(1)
print("製作一杯波霸奶茶")

@計時
def 四季奶綠():
    time.sleep(2)
print("製作一杯四季奶綠")

if __name__ == '__main__':
    波霸奶茶()
    四季奶綠()
複製程式碼

一樣的執行結果,更加精簡的程式碼,相信讀者都體驗到了語法糖的便利,但是要注意一點:大部分的語法糖只是減少我們寫繁瑣程式碼的時間,讓我們把更多的時間花在邏輯流程上,程式碼寫少了不一定就會帶來效能上的提升,底層的程式碼可能還是那些東西!

② 帶引數的裝飾器

上面演示的是無引數的裝飾器的用法,有時我們可能需要傳入一些引數,需要在原閉包的基礎上再加一層閉包,具體流程與程式碼示例如下:

import time

def 計時(配料="珍珠"):
    def decorator(func):
        def inner():
            print("加入配料:%s" % 配料 )
            start = time.time()
            func()
            end = time.time()
            print("耗時", end - start)
            return func
        return inner

    return decorator

@計時(配料='波霸')
def 波霸奶茶():
    time.sleep(1)
    print("製作一杯波霸奶茶")

@計時(配料='椰果')
def 四季奶綠():
    time.sleep(2)
    print("製作一杯四季奶綠")

if __name__ == '__main__':
    波霸奶茶()
    print()
    四季奶綠()
複製程式碼

執行結果如下

加入配料:波霸
製作一杯波霸奶茶
耗時 1.0022242069244385

加入配料:椰果
製作一杯四季奶綠
耗時 2.0034420490264893
複製程式碼

③ 多個裝飾器的執行順序

一個函式可以搭配多個修飾器使用,執行的順序:「從下往上,就近原則,靠近函式定義的先執行」。驗證程式碼示例如下:

def func_a(func):
    def decorator():
        func()
        print("Call func_a")
return decorator

def func_b(func):
    def decorator():
        func()
        print("Call func_b")
return decorator

def func_c(func):
    def decorator():
        func()
        print("Call func_c")
return decorator

@func_a
@func_b
@func_c
def func_t():
pass

if __name__ == '__main__':
    func_t()
複製程式碼

執行結果如下

Call func_c
Call func_b
Call func_a
複製程式碼

④ 四種不同型別的裝飾器

Python中有四種不同型別的裝飾器,分別是:「函式裝飾函式」「函式裝飾類」「類裝飾函式
類裝飾類」, 和類有關的裝飾器可以等後面學完類和物件再回頭看,不用死記,用到的時候
照葫蘆畫瓢改改就好。另外類裝飾函式或類主要依賴類的__call__函式,當使用@形式將裝飾器 附加到函式上時,就會呼叫該函式。

  • 函式裝飾函式」,使用程式碼示例如下:
def func_func(func):
    def decorator(a, b):
        print("函式裝飾的函式名:", func.__name__)
        result = func(a, b)
        return result
return decorator

@func_func
def func_add(a, b):
return a + b

if __name__ == '__main__':
    print(func_add(12))
複製程式碼

執行結果如下

函式裝飾的函式名: func_add
3
複製程式碼
  • 函式裝飾類」,使用程式碼示例如下:
def func_class(cls):
    def decorator(name):
        print("函式裝飾的類:", cls.__name__, name)
        return cls(name)
return decorator

@func_class
class A:
    def __init__(self, n):
        self.n = n
    def show(self):
        print("a = " + self.n)

a = A('123')
a.show()
複製程式碼

執行結果如下

函式裝飾的類: A 123
a = 123
複製程式碼
  • 類裝飾函式」,使用程式碼示例如下:
class class_func:
    def __init__(self, _func):
        self._func = _func

    def __call__(self, name):
        print("類裝飾的函式名:"self._func.__name__, name)
        return self._func(name)

@class_func
def func(a):
    return a
if __name__ == '__main__':
    print(func('123'))
複製程式碼

執行結果如下

類裝飾的函式名: func 123
123
複製程式碼
  • 類裝飾類」,使用程式碼示例如下:
class class_class:
    def __init__(self, _cls):
        self._cls = _cls

    def __call__(self, name):
        print("類裝飾的類的類名:"self._cls.__name__, name)
        return self._cls(name)

@class_class
class A:
    def __init__(self, a):
        self.a = a

    def show(self):
        print('self.a = 'self.a)

if __name__ == '__main__':
    a = A('123')
    a.show()
複製程式碼

執行結果如下

類裝飾的類的類名: A 123
self.a =  123
複製程式碼

如果本文對你有所幫助,歡迎
留言,點贊,轉發
素質三連,謝謝?~


相關文章