[toc]
一聚假、函數(shù)對象
在python中,函數(shù)是一等對象机蔗,“一等對象”滿足下述條件的程序?qū)嶓w:
- 可以被引用
- 可以當作參數(shù)傳遞給函數(shù)
- 能做為函數(shù)的返回結(jié)果
- 可以當作容器數(shù)據(jù)類型的元素蒲祈,例如:可以當作字典中某個key的value
- 在運行時創(chuàng)建
1.可以被引用
def foo():
print('from foo')
func=foo
print(foo)
print(func)
func()
# 輸出:
<function foo at 0x0000000000BAE1E0>
<function foo at 0x0000000000BAE1E0>
from foo
2. 可以當作參數(shù)傳遞給另一個函數(shù)
def foo():
print('from foo')
def bar(func):
print(func)
func() # 此時func() == foo()
bar(foo) #將函數(shù)foo內(nèi)存對象當做參數(shù)傳給bar()
# 輸出:
<function foo at 0x000000000114E1E0>
from foo
3.可以當作一個函數(shù)的返回結(jié)果
def foo():
print('from foo')
def bar(func):
return func
f=bar(foo) # 此時f = foo
print(f)
f() # 此時執(zhí)行f() 相當于執(zhí)行foo()
# 輸出:
<function foo at 0x0000000000B8E1E0>
from foo
4. 當作其他容器類型數(shù)據(jù)的元素
def foo():
print('from foo')
dic={'func':foo} # 定義一個字典dic,將函數(shù)foo的內(nèi)存對象做為value
print(dic['func']) # 相當于打印foo的內(nèi)存對象
dic['func']() # 相當于執(zhí)行foo()函數(shù)
# 輸出:
<function foo at 0x0000000000A4E1E0>
from foo
應用:
def select(sql):
print('========>select')
def insert(sql):
print('========>add')
def delete(sql):
print('=======>delete')
def update(sql):
print('=======>update')
func_dic={
'select':select,
'update':update,
'insert':insert,
'delete':delete
} # 定義一個字典,將上面定義的函數(shù)名做為value
def main():
while True:
sql = input('>>: ').strip()
if not sql:continue
l = sql.split()
cmd=l[0]
if cmd in func_dic:
func_dic[cmd](l)
main()
二、函數(shù)嵌套
1. 函數(shù)的嵌套定義
簡單理解就是在定義一個函數(shù)時萝嘁,函數(shù)體內(nèi)又定義一個子函數(shù)梆掸,示例如下:
def f1():
def f2():
print('from f2')
def f3():
print('from f3')
f3()
f2()
f1()
# 輸出:
from f2
from f3
2. 嵌套函數(shù)的調(diào)用
下面示例是一個求4個數(shù)中最大值的小程序:
# 嵌套函數(shù)
def mymax(x,y):
return x if x > y else y
def four(a,b,c,d):
res1=mymax(a,b) # 調(diào)用mymax()函數(shù)先比較a和b,較大的值給res1
res2=mymax(c,res1)
res3=mymax(d,res2)
return res3
print(four(8,45,9,34))
# 輸出:
45
三酿愧、名稱空間與作用域
1.定義名字的方法
- 導入的模塊名稱time沥潭,如:
import time
- 定義變量的名稱name,如:
name = 'caigy'
- 定義函數(shù)的名稱func嬉挡,如:
def func():
pass
- 定義類的名稱foo钝鸽,如:
class foo:
pass
2. 三種名稱空間
2.1 內(nèi)置名稱空間
隨著Python解釋器的啟動而產(chǎn)生,比如python內(nèi)置的一些函數(shù):sun()庞钢、max()拔恰、min()等,這些函數(shù)隨著python的啟動就定義好了基括,所以在定義名稱時不要與這些關鍵字重名
可以用以下方法查看python的內(nèi)置函數(shù):
import builtins
for i in dir(builtins):
print(i)
2.2 全局名稱空間
py文件的執(zhí)行會產(chǎn)生全局名稱空間颜懊,指的是文件級別定義的名字都會放入該空間
2.3 局部名稱空間
調(diào)用函數(shù)時會產(chǎn)生局部名稱空間,只在函數(shù)調(diào)用時臨時綁定,函數(shù)調(diào)用結(jié)束后解除綁定河爹。
如下示例:
# !/user/bin/env python
# -*- coding:utf-8 -*-
name = 'caigy' # 1
def func():
x = 1 # 2
代碼中:
- 變量name是全局名稱空間
- 變量x在函數(shù)func()中匠璧,是局部名稱空間
3. 作用域
- 全局作用域:內(nèi)置名稱空間、全局名稱空間
- 局部作用域:局部名稱空間
python中名字的查找順序:
局部名稱空間 ---> 全局名稱空間 ---> 內(nèi)置名稱空間
- 查看全局作用域內(nèi)的名字: globals()
- 查看局部作用域內(nèi)的名字: locals()
示例代碼:
name = 'caigy'
def foo():
name = 'egon'
print(name)
print(locals())
print(globals())
foo()
以上代碼輸出結(jié)果:
egon
{'name': 'egon'}
{'__file__': 'D:/PycharmProjects/s17/day04/test.py', 'foo': <function foo at 0x013B5660>, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x01755A90>, 'name': 'caigy', '__name__': '__main__', '__spec__': None, '__package__': None, '__builtins__': <module 'builtins' (built-in)>, '__doc__': None, '__cached__': None}
作用域的有效范圍
- 全局作用域的名字:全局有效咸这,在任何位置都能被訪問到夷恍,除非del刪掉,否則會一直存活到文件執(zhí)行完畢
- 局部作用域的名字:局部有效媳维,只能在局部范圍內(nèi)調(diào)用酿雪,只在函數(shù)調(diào)用時才有效,調(diào)用結(jié)束就失效
注:當全局變量與局部變量同名時侄刽,在定義局部變量的子程序內(nèi)指黎,局部變量起作用;在其它地方全局變量起作用
四州丹、閉包函數(shù)
閉包函數(shù)的特性:
- 定義在一個函數(shù)的內(nèi)部的函數(shù)
- 包含對外部作用域而非全局作用域的引用
該內(nèi)部函數(shù)就稱為閉包函數(shù)
如下示例:
def func():
name = 'caigy'
def foo():
print(name)
return foo
f = func() # func()返回結(jié)果是foo ,所以f = foo
print(f) # 打印的實際上是foo的內(nèi)存對象地址
f()
# 輸出結(jié)果:
<function func.<locals>.foo at 0x01342150>
caigy
如上述代碼中醋安,foo()就是一個閉包函數(shù)
閉包函數(shù)的應用:隨性計算
爬取一個網(wǎng)頁
from urllib.request import urlopen
def index(url):
def get():
return urlopen(url).read()
return get
f= index('http://www.360duohui.com')
res = f().decode('utf-8')
print(res)
上述代碼中get()函數(shù)就是一個閉包函數(shù)
總結(jié):閉包函數(shù)實現(xiàn)了內(nèi)部函數(shù)在外部可以被調(diào)用
獲取閉包函數(shù)所引用的外部變量(非全局變量)的值
def func():
name='caigy'
def foo():
print(name)
return foo
f = func()
print(f.__closure__[0].cell_contents)
# 輸出:
caigy
五、裝飾器(decorator)
裝飾器墓毒,顧名思義茬故,就是用來裝飾用的,具體點說就是給其它程序添加功能的蚁鳖。其本質(zhì)就是函數(shù),功能是為其他函數(shù)添加新功能赁炎。
裝飾器本身可以是任何可調(diào)用對象醉箕,被裝飾的對象也可以是任意可調(diào)用的對象调鬓。
1. 為什么要用裝飾器呢碎浇?
源于程序開的開放封裝原則:
- 對修改是封閉的,對擴展是開放的痰催。
- 裝飾器就是為了在不修改被裝飾對象的源代碼以及調(diào)用方式的前提下姻报,為其添加新功能
如下示例:
def run():
time.sleep(3)
print('already test 3s')
run()
為上述代碼添加一個打印日志的功能:
無參裝飾器
import time
def timer(func):
def wrapper(*args,**kwargs):
start_time = time.time()
func() #3
stop_time = time.time()
print('run is %ss'%(stop_time-start_time))
return wrapper
timer #1
def run():
time.sleep(3)
print('already test 3s')
run() #2
#1 @timer 實際上就是做了run = timer(run) 的操作己英;
#2 因為timer()函數(shù)的返回值是wrapper,所以此時的run = wrapper , 所run()實際就相當于調(diào)用了wrapper()
#3 run 做為參數(shù)傳給timer()函數(shù)吴旋,所以此時func=run损肛,此時的func()相當于調(diào)用了run()
以上代碼的輸出結(jié)果如下:
already test 3s
run is 3.0033679008483887s
2. 附加多個裝飾器
有參裝飾器
import time
def timer(func_1):
def wrapper_1(*args,**kwargs):
start_time = time.time()
func_1(*args,**kwargs) #3
stop_time = time.time()
print('run is %s秒 '%(stop_time-start_time))
return wrapper_1
user_stat = {'name':None,'stats':False}
def auth(func):
def wrapper(*args,**kwargs):
if user_stat['name'] and user_stat['stats']:
print('login successful')
func(*args, **kwargs)
else:
name = input('name: ')
pwd = input('password: ')
if name == 'egon' and pwd == '123':
# 登錄成功后記錄用戶名和登錄狀態(tài),下次調(diào)用時就不用再輸入用戶名登錄了
user_stat['name'] = 'egon'
user_stat['stats'] = True
print('login successful')
func(*args,**kwargs)
# time.sleep(3)
else:
print('login error')
return wrapper
@timer #2
@auth #1
def index():
print('Welcome to Oldboyedu.com')
@auth
def home(name):
print('Welcome to %s homepage'%name)
index() #4
home('egon')
輸出結(jié)果:
name: egon
password: 123
login successful
Welcome to Oldboyedu.com
run is 3.2893900871276855秒
login successful
Welcome to egon homepage
#1 @auth 實際上就是做了index = auth(index)的操作荣瑟,因為auth()函數(shù)的返回值是wrapper,所以index此時等于wrapper治拿,在下方加括號()就可以直接調(diào)用wrapper()函數(shù)了
#2 由于此時@auth 為wrapper, 所以@timer 就是wrapper = timer(wrapper) , timer()的返回值是wrapper_1笆焰,所以此時wrapper = wrapper_1
#3 由于wrapper被當作參數(shù)傳給timer()劫谅,所以此時func_1 = wrapper
#4 在執(zhí)行index()時,相于當于執(zhí)行wrapper_1() ;當程序執(zhí)行到func_1(args,*kwargs)時捏检,此時的func_1 就是 wrapper荞驴,所以就會調(diào)用wrapper(args,*kwargs);當wrapper()函數(shù)內(nèi)的程序執(zhí)行到func(args,*kwargs)時贯城,此時的func = index ,所以用調(diào)用真實定義的index()函數(shù)熊楼;然后返回wrapper_1(),繼續(xù)執(zhí)行func_1()后的代碼冤狡。
六孙蒙、迭代器(iterator)
1. 迭代的概念
重復的過程稱為迭代,每次重復即為一次迭代悲雳,并且每次迭代的結(jié)果作為下一次迭代的初始值挎峦;
不是迭代:
while True: #只滿足重復,因而不是迭代
print('====>')
下面才為迭代
l = [1, 2, 3]
count = 0
while count < len(l):
print('====>', l[count])
count += 1
# 輸出:
====> 1
====> 2
====> 3
注:以上這種方式迭代的是列表合瓢,而列表是有序的對象坦胶,那像無序的對象,比如:字典晴楔,集合顿苇,文件等如何迭代呢?
2. 為什么要有迭代器
對于沒有索引的數(shù)據(jù)類型税弃,必須提供一種不依賴索引的迭代方式
可迭代的對象:內(nèi)置__iter__
方法的對象纪岁,都是可迭代的對象
[1,2].__iter__() # 列表
'hello'.__iter__() # 字符串
(1,2,).__iter__() # 元組
{'a':1,'b':2}.__iter__() # 字典
{1,2,3}.__iter__() # 集合
迭代器: 執(zhí)行__iter__
方法,得到的結(jié)果就是迭代器则果,迭代對象都具體__next__
方法
i = [1,2,3].__iter__()
print(i) # 打印可迭代對象的內(nèi)存地址
print(i.__next__())
print(i.__next__())
print(i.__next__())
print(i.__next__())
# 超出迭代的對象個數(shù)后會拋出異常:StopIteration
# 輸出:
<list_iterator object at 0x011B5AB0>
1
2
3
Traceback (most recent call last):
File "D:/PycharmProjects/s17/day04/test.py", line 209, in <module>
print(i.__next__())
StopIteration
迭代字典
i={'a':1,'b':2,'c':3}.__iter__()
print(i.__next__()) # 迭代字典中的key
print(i.__next__())
print(i.__next__())
print(i.__next__())
# 超出字典i中的鍵值對的個數(shù)幔翰,會拋出異常:StopIteration
換一種方法:
i={'a':1,'b':2,'c':3}
I = iter(i)
print(next(I))
print(next(I))
print(next(I))
注:
__iter__()
=iter()
,__next__()
=next()
,兩種方法得到的結(jié)果是一樣的。
利用while循環(huán)迭代字典
dic={'a':1,'b':2,'c':3}
i=dic.__iter__() # 生成迭代器對象
while True:
try:
key=i.__next__()
print(dic[key])
except StopIteration: # 遇到指定異常后西壮,執(zhí)行子代碼
break
3. 如何判斷一個對象是可迭代對象遗增,還是迭代器對象
- 可迭代對象:只有
__iter__
方法,執(zhí)行該方法得到的是迭代器對象 - 迭代器對象:有
__next__
方法和__iter__
方法款青,迭代器對象執(zhí)行__iter__方法做修,
得到的結(jié)果仍然是它本身。
判斷可迭代的對象需要導入Iterable
模塊
from collections import Iterable,Iterator
f = open('a.txt','w')
f.__iter__()
# 下列數(shù)據(jù)類型都是可迭代的對象
print(isinstance('abc',Iterable)) # 字符串
print(isinstance([1,2,3],Iterable)) # 列表
print(isinstance({'a':1,},Iterable)) # 字典
print(isinstance({1,2,3},Iterable)) # 集合
print(isinstance((1,2,),Iterable)) # 元組
print(isinstance(f,Iterable)) # 文件
# 輸出:
True
True
True
True
True
True
判斷迭代器對象需要導入Iterator
模塊
from collections import Iterable,Iterator
f = open('a.txt','w')
f.__iter__()
# 只有文件是迭代器對象
print(isinstance('abc',Iterator))
print(isinstance([],Iterator))
print(isinstance((),Iterator))
print(isinstance({'a':1},Iterator))
print(isinstance({1,2},Iterator))
print(isinstance(f,Iterator))
# 輸出:
False
False
False
False
False
True
驗證迭代器對執(zhí)行__iter__
方法得到的結(jié)果仍然是其本身
f = open('a.txt','w')
f1 = f.__iter__()
print(f)
print(f1)
print(f1 is f) # 輸出`True`表示結(jié)論正確
# 輸出:
<_io.TextIOWrapper name='a.txt' mode='w' encoding='cp936'>
<_io.TextIOWrapper name='a.txt' mode='w' encoding='cp936'>
True
4. 迭代器的優(yōu)點和缺點
其特點:
- 訪問者不需要關心迭代器內(nèi)部的結(jié)構(gòu)抡草,僅需通過next()方法不斷去取下一個內(nèi)容
- 不能隨機訪問集合中的某個值 饰及,只能從頭到尾依次訪問
- 訪問到一半時不能往回退
- 便于循環(huán)比較大的數(shù)據(jù)集合,節(jié)省內(nèi)存
-
優(yōu)點:
- 提供了一種不依賴于下標的迭代方式
- 就迭代器本身來說康震,更節(jié)省內(nèi)存
-
缺點:
- 無法獲取迭代器對象的長度
- 不如序列類型的數(shù)據(jù)取值靈活旋炒,而且是一次性的,只能往后取值签杈,不能往前退
驗證:只能往后取值瘫镇,不能往前退
l=[10000,2,3,4,5]
i=iter(l)
for item in i:
print(item)
print('=====================')
for item in i:
print(item)
# 輸出:
10000
2
3
4
5
====================
# 通過上述代碼 的結(jié)果可以看出鼎兽,通過列表l生成的迭代器i ,通過for循環(huán)迭代后铣除,再次通過for循環(huán)迭代谚咬,就取不到值了
擴展:enumerate()
方法生成的也是迭代器對象
l=[2,3,4]
i=enumerate(l)
print(next(i))
print(next(i))
print(next(i))
# 輸出:
(0, 2)
(1, 3)
(2, 4)
總結(jié):python中for循環(huán)就是通過迭代器的方式來實現(xiàn)的,而while只是普通的循環(huán)尚粘,for迭代 == while + try ... except(異常處理)
七择卦、生成器(generator)
只要函數(shù)體內(nèi)包含yield關鍵字,該函數(shù)就是生成器函數(shù)
1. 生成器簡單示例
生成器就是迭代器
def index():
print('first')
yield 1
print('second')
yield 2
print('third')
yield 3
g = index()
for i in g:
print(i)
上述代碼輸出:
first
1
second
2
third
3
通過next()方法:
def index():
print('first')
yield 1
print('second')
yield 2
print('third')
yield 3
g = index()
print(next(g)) # next()方法觸發(fā)迭代器g的執(zhí)行郎嫁,進而觸發(fā)函數(shù)的執(zhí)行
print(next(g))
print(next(g))
# 輸出:
first
1
second
2
third
3
生成器應用 :
def counter(n):
print('start...')
i=0
while i < n:
yield i
i+=1
print('end...')
g=counter(5)
print(g) # 這一步不會執(zhí)行counter()函數(shù)秉继,如果是普通函數(shù),就會被執(zhí)行
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
# 輸出:
<generator object counter at 0x0196DB10>
start...
0
1
2
3
4
注:上述代碼中泽铛,迭代器對象g尚辑,只有通過next()方法(調(diào)用時,才會觸發(fā)函數(shù)counter()的執(zhí)行
總結(jié):
yield的功能:
- 相當于為函數(shù)封裝好
__iter__
和__next__
return
只返回一次值盔腔,函數(shù)就終止了杠茬,而yield
能返回多次值,每次返回都會將函數(shù)暫停弛随,下一次next()
會從上一次暫停的位置繼續(xù)執(zhí)行
2. 生成器執(zhí)行流程
def foo():
print('in the foo ...')
food = yield '您好'
print('food >>>',food)
print('我在后面')
food1= yield '你壞'
print('food1 >>> ',food1)
g= foo()
res = next(g)
print(res)
res1 = g.send('x')
print(res1)
##res2= g.send('xx')
'''
生成器執(zhí)行流程:
1.g=foo()瓢喉,只是將foo()生成器對象賦值給g,然后繼續(xù)往下執(zhí)行;
2.遇到next()或g.send('x')就開始執(zhí)行foo()內(nèi)部的代碼舀透,\
執(zhí)行遇到第一個yield時栓票,就暫停(我也理解為進入休眠狀態(tài)),\
并將yield后面的值返回給next(g)愕够,并跳出到foo()外面next(g)所在的那一行逗载,\
將yield返回的值賦值給res
3.res接收yield返回給next(g)的值,然后往下執(zhí)行代碼链烈,打印res的值;
4.當再次遇到next()或g.send('x')時挚躯,喚醒foo()繼續(xù)從上次 \
暫停的位置開始執(zhí)行强衡, 同時將g.send(‘x’)中的'x'發(fā)送 \
給第一個yield,并賦值給food,然后繼續(xù)往下執(zhí)行码荔;
5.當遇到第二個yield時漩勤,進入暫停(休眠),\
同時將yield后面的值返回給g.send('x')缩搅,\
跳出到g.send('x')所在的那一行越败,并將yield返回的值賦值給res1,\
然后繼續(xù)執(zhí)行至結(jié)束硼瓣。
注意:
print(res1)后面沒有代碼了究飞,此時foo()中的food1是空置谦,\
如果print(res1)后面再出現(xiàn)g.send('xx')代碼,\
才會將'xx'發(fā)送給第二個yield亿傅,并賦值給food1;
但是,foo()內(nèi)部會從第二個yield那一行繼續(xù)往下執(zhí)行媒峡,\
如果后面沒有yield關鍵字了,程序就會拋出一個StopIteration異常葵擎。
'''
生成器的應用:
實現(xiàn)Linux命令tail -f a.txt | grep 'python'
的功能
import time
def tail(filepath):
with open(filepath,encoding='utf-8') as f:
f.seek(0,2) # 跳到文件末尾
while True:
line=f.readline().strip()
if line:
yield line #3
else:
time.sleep(0.2)
def grep(pattern,lines): #1
for line in lines: #2
if pattern in line: #4
yield line #5
g=grep('python',tail('a.txt'))
print(g)
for i in g: #6
print(i)
程序解析:
#1 pattern = 'python' , lines = tail('a.txt')
#2 此時的lines是一個迭代器谅阿,經(jīng)過for循環(huán)會觸發(fā)tail('a.txt')函數(shù)的執(zhí)行,這時運行tail()函數(shù)內(nèi)的程序酬滤,會通過while循環(huán)監(jiān)控文件末尾是否有內(nèi)容
#3 如果有新內(nèi)容签餐,則通過yield返回
#4 第#3通過yield返回的line傳給#4行的line
#5 這里將line通過yield返回給#6
#6 這時i = line