一、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是一個生成器,創建l和g的區別僅在於最外層的[]和(),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錯誤,返回值包含在StopIteration的value中,關於如何捕獲錯誤,后面的錯誤處理還會詳細講解。
還可通過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種:
-
集合數據類型,如
list、tuple、dict、set、str等; -
生成器,包括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對象,但list、dict、str雖然是Iterable,卻不是Iterator;把list、dict、str等Iterable變成Iterator可以使用iter()函數
#可迭代對象轉迭代器對象
print(isinstance(iter([]), Iterator))
# True
print(isinstance(iter("abc"), Iterator))
# True
你可能會問,為什么list、dict、str等數據類型是Iterable但不是Iterator?
這是因為Python的Iterator對象表示的是一個數據流,Iterator對象可以被next()函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration錯誤。可以把這個數據流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()函數實現按需計算下一個數據,所以Iterator的計算是惰性的,只有在需要返回下一個數據時它才會計算。Iterator甚至可以表示一個無限大的數據流,例如全體自然數。而使用list是永遠不可能存儲全體自然數的。
小結:
- 凡是可作用於
for循環的對象都是Iterable類型; - 凡是可作用於
next()函數的對象都是Iterator類型,它們表示一個惰性計算的序列; - 集合數據類型如
list、dict、str等是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
