Python開發【第四章】:函數剖析


一、Python函數剖析

1、函數的調用順序

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian

#函數錯誤的調用方式
def func():                     #定義函數func()
    print("in the func")
    foo()                       #調用函數foo()
func()                          #執行函數func()
def foo():                      #定義函數foo()
    print("in the foo")

###########打印輸出###########              #報錯:函數foo沒有定義
#NameError: name 'foo' is not defined


#函數正確的調用方式
def func():                     #定義函數func()
    print("in the func")
    foo()                       #調用函數foo()
def foo():                      #定義函數foo()
    print("in the foo")
func()                          #執行函數func()

###########打印輸出###########              
#in the func
#in the foo

總結:被調用函數要在執行之前被定義 

 

2、高階函數

滿足下列條件之一就可成函數為高階函數

  • 某一函數當做參數傳入另一個函數中

  • 函數的返回值包含一個或多個函數

剛才調用順序中的函數稍作修改就是一個高階函數

#高階函數
def func():                     #定義函數func()
    print("in the func")
    return foo()                #調用函數foo()
def foo():                      #定義函數foo()
    print("in the foo")
    return 100

res = func()                     #執行函數func()
print(res)                       #打印函數返回值

###########打印輸出###########
#in the func
#in the foo
#100

從上面的程序得知函數func的返回值為函數foo的返回值,如果foo不定義返回值的話,func的返回值默認為None;

下面我來看看更復雜的高階函數:

#更復雜的高階函數
import time                 #調用模塊time
def bar():
    time.sleep(1)
    print("in the bar")
def foo(func):
    start_time=time.time()
    func()
    end_time=time.time()
    print("func runing time is %s"%(end_time-start_time))
    
foo(bar)
###########打印輸出###########
#in the bar
#func runing time is 1.0000572204589844

其實上面這段代碼已經實現了裝飾器一些功能,即在不修改bar()代碼的情況下,給bar()添加了功能;但是改變了bar()調用方式

下面我們對上面的code進行下修改,不改變bar()調用方式的情況下進行功能添加

#更復雜的高階函數,不改變調用方式
import time                 #調用模塊time
def bar():
    time.sleep(1)
    print("in the bar")
def foo(func):
    start_time=time.time()
    print("in the foo")
    return func            #返回bar函數的內存地址
    end_time=time.time()
    print("func runing time is %s"%(end_time-start_time))

bar = foo(bar)              #bar重新賦值
bar()
###########打印輸出###########
#in the foo
#in the bar

我們沒有對bar()源碼進行過修改,也沒有改變bar()的調用方式,當執行bar()函數時,多加了一些功能,裝飾器的一些雛形已經呈現;但是我們又發現之前添加的計算bar()執行時間的功能沒有打印出來,return執行后函數就結束了。

 

3、內嵌函數和作用域

定義:在一個函數體內創建另外一個函數,這種函數就叫內嵌函數(基於python支持靜態嵌套域)

#內嵌函數示例
def foo():
    print("in the foo")
    def bar():
        print("in the bar")
    bar()

foo()
###########打印輸出###########
#in the foo
#in the bar

嵌套函數有什么用呢?我們暫時先記住這個內容

局部作用域和全局作用域的訪問順序

#嵌套函數變量與全部變量
x = 0
def grandpa():
    x=1
    def dad():
        x=2
        def son():
            x=3
            print(x)
        son()
    dad()

grandpa()
print(x)
###########打印輸出###########
# 3
# 0 

注:內嵌函數中定義的函數在全局中是無法直接執行的

 

4、裝飾器

定義:本質是函數(裝飾其他函數),為其他函數添加附加功能的。

遵循原則:①不能修改被裝飾函數的源代碼

     ②不能修改被裝飾函數的調用方式

組成:裝飾器由高階函數+內嵌函數組成

之前說了那么多其實都是了給裝飾器做鋪墊,回到剛才高階函數中最后一個示例,能不能給函數加上運算時間計算?

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian

#裝飾器
import time
def timer(func):
    def deco():
        start_time=time.time()
        func()                    #執行形參func()
        end_time=time.time()
        print("func runing time is %s"%(end_time-start_time))
    return deco                #返回函數deco的內存地址
def test1():
    print("in the test1")
    time.sleep(1)

test1 = timer(test1)            #重新賦值test1  此時test1=deco的內存地址
test1()                         #執行test1
###########打印輸出###########
#in the test1
#func runing time is 1.0000572204589844

現在我們已經實現了裝飾器的功能,但是如果test1有形參的話,上面的代碼就會報錯了,下面我們對代碼做下修改

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian

#裝飾器
import time
def timer(func):
    def deco(*args,**kwargs):
        start_time=time.time()
        func(*args,**kwargs)                    #執行形參func()
        end_time=time.time()
        print("func runing time is %s"%(end_time-start_time))
    return deco                #返回函數deco的內存地址
@timer                          #test1 = timer(test1)  test1=deco
def test1(name):
    print("in the test1 name %s"%name)
    time.sleep(1)

test1("lzl")                         #執行test1
###########打印輸出###########
#in the test1
#func runing time is 1.0000572204589844

 上面的代碼是不是覺得很完美了,呵呵,假如test1()有return返回值怎么辦?你會發現最后執行test1返回值丟失了,所以要對上面的代碼再完善一下了。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian

#裝飾器
import time
def timer(func):
    def deco(*args,**kwargs):
        start_time=time.time()
        res = func(*args,**kwargs)                    #執行形參func()
        end_time=time.time()
        print("func runing time is %s"%(end_time-start_time))
        return res
    return deco                #返回函數deco的內存地址
@timer                          #test1 = timer(test1)  test1=deco
def test1(name):
    print("in the test1 name %s"%name)
    time.sleep(1)
    return "return form test1"

print(test1("lzl"))                  #執行test1
###########打印輸出###########
#in the test1
#func runing time is 1.0000572204589844
#return form test1

 好了現在我們探討一個問題,函數可以被多個裝飾器裝飾嗎?!

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian

#函數用多個裝飾器
def w1(func):
    def inner(*args, **kwargs):
        print("in the w1")
        return func(*args, **kwargs)
    return inner
def w2(func):
    def inner(*args, **kwargs):
        print("in the w2")
        return func(*args, **kwargs)
    return inner
@w1
@w2
def f1(*args, **kwargs):
    print("in the f1")

f1()
###########打印輸出###########
#in the w1
#in the w2
#in the f1

終極版裝飾器來了......

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian

#終極版裝飾器
def Before(*args, **kwargs):
    print("before")
def After(*args, **kwargs):
    print("after")

def Filter(before_func, after_func):
    def outer(main_func):
        def wrapper(*args, **kwargs):
            before_result = before_func(*args, **kwargs)
            if (before_result != None):
                return before_result
            main_result = main_func(*args, **kwargs)
            if (main_result != None):
                return main_result
            after_result = after_func(*args, **kwargs)
            if (after_result != None):
                return after_result
        return wrapper
    return outer

@Filter(Before, After)      #Filter(Before,After)=outer  Index=outer(Index)=wrapper
def Index(*args, **kwargs):
    print("index")

Index()                     #Index() = wrapper()
###########打印輸出###########
#before
#index
#after

  

 5、生成器

 學習生成器之前,我們先來看看什么是列表生成式

#列表生成式
b = [ i*2 for i in range(10)]
print(b)

###########打印輸出###########
#[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

通過列表生成式,我們可以直接創建一個列表。但是,受到內存限制,列表容量肯定是有限的。而且,創建一個包含100萬個元素的列表,不僅占用很大的存儲空間,還需要花費很長時間,如果我們僅僅需要訪問前面幾個元素,那后面絕大多數元素占用的空間都白白浪費了。所以,如果列表元素可以按照某種算法推算出來,那我們是否可以在循環的過程中不斷推算出后續的元素呢?這樣就不必創建完整的list,從而節省大量的空間。在Python中,這種調用時才會生成相應數據的機制,稱為生成器:generator

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

#生成器
l = [ i*2 for i in range(10)]
print(l)

g = (i*2 for i in range(10))
print(g)

###########打印輸出###########
#[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
#<generator object <genexpr> at 0x0064AAE0>

print(g) 打印出來的信息顯示g是一個生成器,創建lg的區別僅在於最外層的[]()l是一個list,而g是一個generator;我們可以直接打印出list的每一個元素,但我們怎么打印出generator的每一個元素呢?如果要一個一個打印出來,可以通過next()函數獲得generator的下一個返回值

#生成器next打印
print(next(g))
#.........                 不斷next 打印10次
#..........
print(next(g))

###########打印輸出###########
#0
#........
#18
#Traceback (most recent call last):
#  File "<stdin>", line 1, in <module>
#StopIteration

我們講過,generator保存的是算法,每次調用next(g),就計算出g的下一個元素的值,直到計算到最后一個元素,沒有更多的元素時,拋出StopIteration的錯誤。

上面這種不斷調用next(g)實在是太變態了,正確的方法是使用for循環,因為generator也是可迭代對象,所以,我們創建了一個generator后,基本上永遠不會調用next(),而是通過for循環來迭代它,並且不需要關心StopIteration的錯誤

#生成器for調用
g = (i*2 for i in range(10))        #不用擔心出現StopIteration錯誤
for i in g:
    print(i)

###########打印輸出###########
# 0
# 2
# 4
# 6
# 8
# 10
# 12
# 14
# 16
# 18

generator非常強大。如果推算的算法比較復雜,用列表生成式轉換的生成器無法去實現時,我們還可以用函數來實現。比如,著名的斐波拉契數列(Fibonacci)

#函數表示斐波拉契數列
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n += 1
    return 'done'

fib(5)
###########打印輸出###########
# 1
# 1
# 2
# 3
# 5

仔細觀察,可以看出,fib函數實際上是定義了斐波那契數列的推算規則,可以從第一個元素開始,推算出后續任意的元素,這種邏輯其實非常類似generator;也就是說,上面的函數和generator僅一步之遙,那我們能不能把上面的函數變成一個生成器呢?

#斐波拉契數列轉換為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'

print(type(fib(5)))     #打印fib(5)的類型
for i in fib(5):        #for循環去調用
    print(i)
###########打印輸出###########
# <class 'generator'>
# 1
# 1
# 2
# 3
# 5

要把fib函數變成generator,只需要把print(b)改為yield b就可以了,這就是定義generator的另一種方法。如果一個函數定義中包含yield關鍵字,那么這個函數就不再是一個普通函數,而是一個generator

但是用for循環調用generator時,會發現拿不到generator的return語句的返回值,也就是return的值沒有打印出來,現在我們來看看怎么去打印generator的返回值

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian

#獲取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'

g = fib(5)
while True:
    try:
        x = next(g)
        print( x)
    except StopIteration as e:
        print(e.value)
        break
###########打印輸出###########
# 1
# 1
# 2
# 3
# 5
# done

如果想要拿到返回值,必須捕獲StopIteration錯誤,返回值包含在StopIterationvalue中,關於如何捕獲錯誤,后面的錯誤處理還會詳細講解。 

還可通過yield實現在單線程的情況下實現並發運算的效果

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian

import time
def consumer(name):
    print("%s 准備吃包子啦!" %name)
    while True:
       baozi = yield

       print("包子[%s]來了,被[%s]吃了!" %(baozi,name))


def producer(name):
    c = consumer('A')
    c2 = consumer('B')
    c.__next__()        #c.__next__()等同於next(c)
    c2.__next__()
    print("老子開始准備做包子啦!")
    for i in range(10):
        time.sleep(1)
        print("%s做了2個包子!"%(name))
        c.send(i)
        c2.send(i)

producer("lzl")

 

6、迭代器

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

  • 集合數據類型,如listtupledictsetstr等;

  • 生成器,包括generator和帶yield的generator function;

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

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

#可迭代對象
from collections import Iterable

print(isinstance([], Iterable))
# True
print(isinstance("abc", Iterable))
# True
print(isinstance((x for x in range(10)), Iterable))
# True
print(isinstance(100, Iterable))
# False

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

重點來了....*可以被next()函數調用並不斷返回下一個值的對象稱為迭代器Iterator

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

#迭代器對象
from collections import Iterator

print(isinstance([], Iterator))
# True
print(isinstance("abc", Iterator))
# False
print(isinstance((x for x in range(10)), Iterator))
# True
print(isinstance(100, Iterator))
# False

由上面可知,生成器都是Iterator對象,但listdictstr雖然是Iterable,卻不是Iterator;把listdictstrIterable變成Iterator可以使用iter()函數

#可迭代對象轉迭代器對象
print(isinstance(iter([]), Iterator))
# True
print(isinstance(iter("abc"), Iterator))
# True

你可能會問,為什么listdictstr等數據類型是Iterable但不是Iterator

這是因為Python的Iterator對象表示的是一個數據流,Iterator對象可以被next()函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration錯誤。可以把這個數據流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()函數實現按需計算下一個數據,所以Iterator的計算是惰性的,只有在需要返回下一個數據時它才會計算。Iterator甚至可以表示一個無限大的數據流,例如全體自然數。而使用list是永遠不可能存儲全體自然數的。

小結:

  • 凡是可作用於for循環的對象都是Iterable類型;
  • 凡是可作用於next()函數的對象都是Iterator類型,它們表示一個惰性計算的序列;
  • 集合數據類型如listdictstr等是Iterable但不是Iterator,不過可以通過iter()函數獲得一個Iterator對象;

Python的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

 

  

 

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM