Python之路【第五篇】: 函数、闭包、装饰器、迭代器、生成器

时间:2023-03-09 16:02:48
Python之路【第五篇】: 函数、闭包、装饰器、迭代器、生成器

目录

  • 函数补充进阶
    • 函数对象
    • 函数的嵌套
    • 名称空间与作用域
    • 闭包函数
  • 函数之装饰器
  • 函数之可迭代对象
  • 函数之迭代器
  • 函数之生成器
  • 面向过程的程序设计思想

一、函数进阶之函数对象

1. 函数对象

  秉承着一切皆对象的理念,我们再次回头来看函数(function)。函数也是一个对象,具有属性(可以使用dir()查询)。作为对象,它还可以赋值给给一个变量、可以作为元素添加到集合对象中、可作为参数值传递给其它函数,还可以当做函数的返回值,这些特性就是第一类对象所特有的。

  • 函数身为一个对象,拥有对象模型的三个通用属性:id、类型、和值。
#!/usr/bin/env python
#-*- coding:utf-8 -*- def foo():
print('from foo') print(id(foo)) # id属性
print(type(foo)) # 类型
func=foo # 将函数名赋值给一个变量
print(foo) # 输出函数的内存地址
print(func) # func变量指向foo函数的内存地址
func() # 加()调用执行 # 结果
'''
7266512
<class 'function'>
<function foo at 0x00000000006EE0D0>
<function foo at 0x00000000006EE0D0>
from foo
'''

函数对象

  • 函数可以被引用
#!/usr/bin/env python
# -*- coding:utf-8 -*-
def foo():
print('from foo') foo()
func=foo #引用,赋值
print(foo)
print(func)
func()
#结果
'''
from foo
<function foo at 0x10eed8f28>
<function foo at 0x10eed8f28>
from foo
'''

函数的引用

  • 可以当作参数传递,即赋值给变量或当作实参传递
def foo():
print('from foo') def bar(func): # 参数func为foo
print(func) # func参数的内存地址
func() # 加()调用func bar(foo) # 将foo函数对象作为参数传递给bar函数并调用
#结果
'''
<function foo at 0x00000000007EE0D0>
from foo
'''

函数的引用

  • 函数可以是返回值
def foo():
print('from foo') def bar(func): # 参数func为foo函数名
return func # 返回foo函数的内存地址 f=bar(foo) # 调用bar函数并将返回的foo函数内存地址赋值给f变量
print(f) # 输出对应的foo内存地址
f() # 加()即调用,此处相当于调用foo函数,即f()=foo()
# 结果
'''
<function foo at 0x0000000000D4E0D0>
from foo
'''

函数作为返回值

  • 可以当作容器类型的元素

  注: 函数对应可以存放在容器元素(元组、列表、字典)和变量中

def foo():
print('from foo') dic = {'func':foo} # 将foo函数对象作为value放入字典中 foo() # 调用foo函数
print(dic['func']) # 字典dic的key值func对应的value为foo函数的内存对象,即foo函数对象作为容器中的一个元素存放
dic['func']() # 内存地址加()便可以调用,即dic['func]()=foo()
#结果
'''
from foo
<function foo at 0x00000000006EE0D0>
from foo
'''

函数作为容器的元素

  • 应用
# def select(sql):
# print('----〉select:%s' % sql)
#
# def insert(sql):
# print('----〉insert:%s' % sql)
#
# def update(sql):
# print('----〉update:%s' % sql)
#
# def delect(sql):
# print('----〉delect:%s' % sql)
#
# sql_dic = {
# 'select':select,
# 'delect':delect,
# 'insert':insert,
# 'update':update
# }
# def main():
# while True:
# sql = input('sql>>>').strip()
# if not sql:continue
# sql_l = sql.split(' ')
# if sql_l[0] in sql_dic:
# sql_dic[sql_l[0]](sql_l)
#
# main()
'''
结果:
sql>>>select * form fafafa
----〉select:['select', '*', 'form', 'fafafa']
sql>>>insert * faormafa faf a
----〉insert:['insert', '*', 'faormafa', '', 'faf', 'a']
sql>>>
'''

应用

2. 函数嵌套

  • 函数的嵌套定义

定义: 所谓嵌套,并不像其他语言中的在一个函数中调用另一个函数,而是在定义一个函数的时候,函数体里还能定义另一个函数。

为什么?因为函数是用def语句定义的,凡是其他语句可以出现的地方,def语句同样可以出现。
像这样定义在其他函数内的函数叫做内部函数,内部函数所在的函数叫做外部函数。当然,我们可以多层嵌套,这样的话,除了最外层和最内层的函数之外,其它函数既是外部函数又是内部函数。

特性: 内函数可以访问外函数的作用域。

def f1():      # def 关键字 函数f1
def f2(): # 函数f2
print('from f2')
def f3(): # 函数f3
print('from f3')
f3() # 调用f3
f2() # 调用 f1() # 调用f1函数 # 结果
'''
from f2
from f3
'''
  • 函数的嵌套调用
# 判断两个数字的大小,返回最大值
def max2(x,y):
return x if x > y else y # 判断4个数字的大小
def max4(a,b,c,d):
res1=max2(a,b) # 调用msx2函数
res2=max2(res1,c)
res3=max2(res2,d)
return res3 print(max4(10,99,31,22))
# 结果
'''
99
'''

函数的嵌套调用

3. 命名空间与作用域

共三种名称空间:

  • 内置名称空间  

  • 全局名称空间

  • 局部名称空间

a. 内置名称空间: 随着python解释器的启动而产生

当使用内建模块中函数或其它功能时,可以直接使用,不用添加内建模块的名字;但是,如果想要向内建模块中添加一些功能,以便在任何函数中都能直接使用而不 用再进行import,这时,就要导入内建模块,在内建模块的命名空间(即__dict__字典属性)中添加该功能。

print(sum)
print(max)
print(min) # built-in
'''
<built-in function sum>
<built-in function max>
<built-in function min>
'''
# 在Python2.X版本中,内建模块被命名为__builtin__,而到了Python3.X版本中,却更名为builtins。
import builtins
for i in dir(builtins):
print(i)

内置名称空间

b. 全局名称空间:文件的执行会产生全局名称空间,指的是文件级别定义的名字都会放入该空间

x=1         # 全局
def func(): # 全局
money=2000
x=2 # 局部
print('from func') print(x)
print(func)
func()
print(x)
print(money) # 输出失败:NameError: name 'money' is not defined 未定义 # 结果
'''
1
<function func at 0x0000000000A5E1E0>
from func
1
'''

全局名称空间

c. 局部名称空间:调用函数时会产生局部名称空间,只在函数调用时临时绑定,调用结束解绑定

x=10000     # 全局
def func():
x=1 # 局部,调用结束后即刻销毁
def f1():
pass

局部名称空间

4. 作用域

命名空间的可见性就是作用域

  • 全局作用域:内置名称空间,全局名称空间
  • 局部作用域:局部名称空间

代码中名字的查找顺序:局部名称空间--->全局名层空间--->内置名称空间

a. 查看全局作用域内的函数:gloabls()

b. 查看局部作用域内的函数:locals()

生命周期

a. 全局作用域的名字:全局有效,在任何位置都能被访问到,除非del删掉,否则会一直存活到文件执行完毕

b. 局部作用域的名字:局部有效,只能在局部范围调用,只在函数调用时才有效,调用结束就失效

x=1000
x=1000
def func():
x=2 print(globals()) # 查看全局作用域内的名字:gloabls()
print(locals()) # 查看全局作用域内的名字:gloabls(),此处由于在文件级别查看,所以globals() is locals()=True
print(globals() is locals())
'''
{'__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000000000685B70>, '__package__': None, '__doc__': None, '__name__': '__main__', 'func': <function func at 0x0000000000B5E1E0>, '__spec__': None, '__cached__': None, 'x': 1000, '__file__': 'E:/YQLFC/study/day04/day4/名称空间与作用域.py', '__builtins__': <module 'builtins' (built-in)>}
{'__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000000000685B70>, '__package__': None, '__doc__': None, '__name__': '__main__', 'func': <function func at 0x0000000000B5E1E0>, '__spec__': None, '__cached__': None, 'x': 1000, '__file__': 'E:/YQLFC/study/day04/day4/名称空间与作用域.py', '__builtins__': <module 'builtins' (built-in)>}
True
'''

示例1

x=1000
def func(y):
x=2
print(locals()) # 查看全局作用域内的名字
print(globals()) # 查看全局作用域内的名字
print(globals() is locals()) func(1) #结果
'''
{'y': 1, 'x': 2}
{'__name__': '__main__', '__builtins__': <module 'builtins' (built-in)>, 'func': <function func at 0x0000000000A5E1E0>, '__doc__': None, '__file__': 'E:/YQLFC/study/day04/day4/名称空间与作用域.py', '__cached__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000000009F5B70>, 'x': 1000, '__spec__': None}
False
'''

示例2

5. 闭包函数

闭包函数必须满足以下两个条件:

  • 定义在内部函数
  • 包含对外部作用域而非全局作用域的引用,该内部函数就称为闭包函数

简单来说: 在函数A的函数体中定义了函数B,且函数B引用了函数A中的变量,并且调用函数A返回函数B的函数对象,那么此时函数B就是闭包函数。

# 定义实例,对象隐藏,全局不可见
# def f1():
# # x内部隐藏,全局不可见
# x = 1
# def f2():
# print(x)
#
# return f2 # 返回函数f2对象
#
# f=f1()
# # print(f)
# # x因为隐藏,所以全局作用域无效
# x=100000000
# f()
'''
结果:
1
'''

闭包示例

闭包应用: 惰性计算

#闭包应用:惰性计算
from urllib.request import urlopen def index(url):
def get():
return urlopen(url).read() # 此处引用了外层作用域url变量 return get # 返回函数对象 oldboy=index('http://crm.oldboyedu.com') # 调用index函数 print(oldboy().decode('utf-8')) # 加()即调用 # 闭包函数相对与普通函数会多出一个__closure__的属性,里面定义了一个元组用于存放所有的cell对象,
# 每个cell对象一一保存了这个闭包中所有的外部变量
print(oldboy.__closure__[0].cell_contents)
#结果
'''
http://crm.oldboyedu.com
''' x=1
# y=2
def f1():
# x=1
y=2
def f2():
print(x,y) # 此处引用外部变量y
return f2 f=f1()
print(f.__closure__[0].cell_contents)
# 结果
'''
2
'''

应用

二、函数之装饰器(Decorators)

1. 定义

器即函数

装饰即修饰,意指为其他函数添加新功能

装饰器定义:本质就是函数,功能是为其他函数添加新功能

装饰器本身可以是任何可调用对象,被装饰的对象也可以是任意可调用对象

2. 装饰器需要遵循的原则

1.不修改被装饰函数的源代码(开放封闭原则)

2.为被装饰函数添加新功能后,不修改被修饰函数的调用方式

3. 装饰器的实现

装饰器的功能是将被装饰的函数当作参数传递给与装饰器对应的函数(名称相同的函数),并返回包装后的被装饰的函数”

直接看示意图,其中 a 为与装饰器 @a 对应的函数, b 为装饰器修饰的函数,装饰器@a的作用是:

Python之路【第五篇】: 函数、闭包、装饰器、迭代器、生成器

简而言之:@a 就是将 b 传递给 a(),并返回新的 b = a(b)

装饰器(decorator)是干嘛的?对于受到封装的原函数来说,装饰器能够在那个函数执行前或者执行后分别运行一些代码,使得可以再装饰器里面访问并修改原函数的参数以及返回值,以实现约束定义、调试程序、注册函数等目标。装饰器一般返回一个包装器(wrapper),而functools.wraps就是装饰包装器的装饰器。

1. 装饰器的基本实现

import time

def timmer(func):       # 传入一个函数对象,有且只有一个参数
def wrapper(): # 接收多个值
start_time=time.time() # 开始时间
res=func() # 此处func()=index()
stop_time=time.time() # 结束时间
print('run time is %s' %(stop_time-start_time))
return wrapper # 返回函数对象 @timmer # 将装饰器的语法糖,在需要装饰的函数正上方,形如@func index=timmer(index)
def index():
time.sleep(3)
print('welcome to index') index()
# 结果
'''
welcome to index
run time is 3.0
'''

装饰器的基本写法

2. 装饰多个函数且被装饰函数有参数

'''
2、多实例添加,及传参数函数
'''
import time
def timmer(func):
# 传递参数,保证通用性,应为可变长参数(*args,**kwargs),可接受所有类型的实参
def wrapper(*args,**kwargs):
start_time=time.time()
# 传递参数,保证通用性,应为可变长参数(*args,**kwargs),可接受所有类型的实参
# 有返回值,存值
res=func(*args,**kwargs)
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
# 有返回值,返回
return res
return wrapper @timmer #index=timmer(index)
def index():
time.sleep(3)
print('welcome to index')
# 有返回值,定义
return 1 #再次调用只需要加@timmer
@timmer
def foo(name):
time.sleep(1)
print('from foo') # 有返回值,传值打印
res=index() #wrapper()
print(res) res1=foo('shuke') #res1=wrapper('egon')
print(res1)
'''
运行结果:
welcome to index
run time is 3.000380039215088
1
from foo
run time is 1.0001308917999268
None
'''

装饰多个函数及函数携带参数

3. 模拟用户登陆,一次登陆,多次使用

# 定义一个字典用于维护用户登陆状态
login_user={'user':None,'status':False} def auth(func):
def wrapper(*args,**kwargs): # 接收多个参数值
if login_user['user'] and login_user['status']: # 字典key对应的value有值,直接调用
res=func(*args,**kwargs)
return res
else:
name=input('>>: ')
password=input('>>: ')
if name == 'shuke' and password == '': # 用户登陆成功,修改字典中的用户登陆状态
login_user['user']='shuke'
login_user['status']=True
print('\033[45mlogin successful\033[0m')
res=func(*args,**kwargs)
return res
else:
print('\033[45mlogin err\033[0m')
return wrapper @auth
def index():
print('welcome to index page')
@auth
def home(name):
print('Hello %s,welcome to home page' %name)
index()
home('shuke') #结果
'''
login successful
welcome to index page
Hello shuke,welcome to home page
'''

装饰器的使用

4. 多装饰器,顺序执行

import time
def timmer(func):
def wrapper(*args,**kwargs):
print('timmer--->wrapper')
start_time=time.time()
res=func(*args,**kwargs)
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
return res
return wrapper login_user={'user':None,'status':False}
def auth(func):
def wrapper(*args,**kwargs):
print('auth--->wrapper')
if login_user['user'] and login_user['status']:
res=func(*args,**kwargs)
return res
else:
name=input('name: ')
password=input('pwd: ')
if name == 'shuke' and password == '':
login_user['user']='shuke'
login_user['status']=True
print('\033[45mlogin successful\033[0m')
res=func(*args,**kwargs)
return res
else:
print('\033[45mlogin err\033[0m')
return wrapper # 顺序执行
@auth # timmer = auth--->wrapper--->timmer
@timmer # index = auth--->wrapper--->timmer--->wrapper(index)
def index():
time.sleep(2)
print('welcome to index page') # 顺序执行,调转则timmer包含auth增加了程序执行时间
@timmer # auth = timmer--->wrapper--->auth
@auth # home = timmer--->wrapper--->auth---->wrapper(home)
def home(name):
time.sleep(2)
print('Hello %s, welcome to home page' %name)
index()
home('shuke')
'''
结果:
auth--->wrapper
name: shuke
pwd: 123
login successful
timmer--->wrapper
welcome to index page
run time is 2.0
timmer--->wrapper
auth--->wrapper
Hello shuke, welcome to home page
run time is 2.0
'''

多个装饰器,自上而下执行

5. 带参数的装饰器

import time
def timmer(func):
def wrapper(*args,**kwargs):
print('====>timmer.wrapper')
start_time=time.time()
res=func(*args,**kwargs) #auth_wrapper
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
return res
return wrapper login_user={'user':None,'status':False}
def auth(driver='file'):
def auth2(func):
def wrapper(*args,**kwargs):
print('=======>auth.wrapper')
time.sleep(5)
if driver == 'file':
if login_user['user'] and login_user['status']:
res=func(*args,**kwargs)
return res
else:
name=input('>>: ')
password=input('>>: ')
if name == 'shuke' and password == '':
login_user['user']='shuke'
login_user['status']=True
print('\033[45mlogin successful\033[0m')
res=func(*args,**kwargs)
return res
else:
print('\033[45mlogin err\033[0m')
elif driver == 'ldap':
print('==========ldap的认证')
elif driver == 'mysql':
print('==========mysql的认证')
return func(*args,**kwargs)
else:
print('=========未知的认证来源')
return wrapper
return auth2 # 顺序执行,index函数的装饰器参数为file,走mysql部分的认证逻辑
@auth('file') #@auth2====>index=auth2(index)===>index=auth_wrapper
@timmer #index=timmer(auth_wrapper) #index=timmer_wrapper
def index():
time.sleep(3)
print('welcome to index page') # home函数的装饰器参数为mysql,走mysql部分的认证逻辑
@auth(driver='mysql')
def home(name):
print('Hello %s, welcome to home page' %name)
index() #timmer_wrapper()
home('shuke') #wrapper('shuke') '''
# 结果
=======>auth.wrapper
>>: shuke
>>: 123
login successful
====>timmer.wrapper
welcome to index page
run time is 3.0
=======>auth.wrapper
==========mysql的认证
Hello shuke, welcome to home page
'''

有参装饰器

6. python functools.wraps装饰器模块

先来看一个不使用functools.wraps的装饰器例子。

def tracer(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
print('%s(%r,%r)->%r'%(func.__name__,args,kwargs,result))
return result
return wrapper @tracer
def fibonacci(n):
if n in (0,1):
return n
return (fibonacci(n-1)+fibonacci(n-2)) fibonacci(3)
print(fibonacci)
print('help:')
help(fibonacci) '''
# 结果
fibonacci((1,),{})->1
fibonacci((0,),{})->0
fibonacci((2,),{})->1
fibonacci((1,),{})->1
fibonacci((3,),{})->2
<function tracer.<locals>.wrapper at 0x00000000007DE840>
help:
Help on function wrapper in module __main__: wrapper(*args, **kwargs)
'''

未使用functools.wraps

可以看到,装饰器完全可以正常工作。。。

但是,函数的名字变成装饰器中的包装器了!!!help内置函数也失效了

也就是说,原函数的属性失效了

如果想要保留原函数的属性,就可以用到functools.wraps了

from functools import wraps
def tracer(func):
@wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
print('%s(%r,%r)->%r'%(func.__name__,args,kwargs,result))
return result
return wrapper @tracer
def fibonacci(n):
if n in (0,1):
return n
return (fibonacci(n-1)+fibonacci(n-2)) fibonacci(3)
print(fibonacci)
print('help:')
help(fibonacci) '''
# 结果
fibonacci((1,),{})->1
fibonacci((0,),{})->0
fibonacci((2,),{})->1
fibonacci((1,),{})->1
fibonacci((3,),{})->2
<function fibonacci at 0x0000000000A2E9D8>
help:
Help on function fibonacci in module __main__: fibonacci(n)
'''

使用functools.wraps

三、可迭代对象

1.迭代的概念

上一次输出的结果为下一次输入的初始值,重复的过程称为迭代,每次重复即一次迭代,并且每次迭代的结果是下一次迭代的初始值

注: 循环不是迭代

while True: #只满足重复,因而不是迭代
print('====>')

列表、元组、字符串都是可迭代对象

2.可迭代的对象

注: 内置__iter__方法的,都是可迭代的对象。

[1,2].__iter__()
'hello'.__iter__()
(1,2).__iter__()
{'a':1,'b':2}.__iter__()
{1,2,3}.__iter__()
总结: 列表、字符串、元组、字典、集合等都是可迭代对象

四、迭代器

1. 为什么要有迭代器?

对于没有索引的数据类型,必须提供一种不依赖索引取值的迭代方式

# 索引取值,有序
[1,2].__iter__()
'hello'.__iter__()
(1,2).__iter__()
# 无序的
{'a':1,'b':2}.__iter__()
{1,2,3}.__iter__()

2. 迭代对象与迭代器之间的关系

如下图所示:

Python之路【第五篇】: 函数、闭包、装饰器、迭代器、生成器

这里x是一个列表,为可迭代对象,使用iter()方法后,此时,得到一个迭代器,迭代器内部持有一个状态,该状态用于记录当前迭代所在的位置,以方便下次迭代的时候获取正确的元素。

迭代器有一种具体的迭代器类型,比如list_iteratorset_iterator。可迭代对象实现了__iter__方法,该方法返回一个迭代器对象,可以使用next()方法依次获取迭代器里面的元素,

当迭代器元素全部被获取后,此时下次迭代会得到一个StopIteration错误。

3. 迭代器定义

迭代器:可迭代对象执行__iter__方法,得到的结果就是迭代器,迭代器对象有__next__方法

它是一个带状态的对象,他能在你调用next()方法的时候返回容器中的下一个值,任何实现了__iter____next__()方法的对象都是迭代器,__iter__返回迭代器自身,__next__返回容器中的下一个值,如果容器中没有更多元素了,则抛出StopIteration异常。

迭代器的实现

i={'a':1,'b':2,'c':3}.__iter__()

print(i.__next__())
print(i.__next__())
print(i.__next__())
print(i.__next__())
'''
a
b
c
Traceback (most recent call last):
File "E:/YQLFC/study/day04/day4/迭代器.py", line 64, in <module>
print(i.__next__())
StopIteration
'''

迭代器的实现

每次调用next()方法的时候做两件事:

  1. 为下一次调用next()方法修改状态
  2. 为当前这次调用生成返回结果

迭代器就像一个懒加载的工厂,等到有人需要的时候才给它生成值返回,没调用的时候就处于休眠状态等待下一次调用,对于处理大数据量的列表、元组、文件等可以很好的节约内存的使用。

4.如何判断一个对象是可迭代的对象,还是迭代器对象?

from collections import Iterable,Iterator

'abc'.__iter__()
().__iter__()
[].__iter__()
{'a':1}.__iter__()
{1,2}.__iter__() f=open('a.txt','w')
f.__iter__() # 下列数据类型都是可迭代的对象
print(isinstance('abc',Iterable))
print(isinstance([],Iterable))
print(isinstance((),Iterable))
print(isinstance({'a':1},Iterable))
print(isinstance({1,2},Iterable))
print(isinstance(f,Iterable)) # 只有文件是迭代器对象
print(isinstance('abc',Iterator))
print(isinstance([],Iterator))
print(isinstance((),Iterator))
print(isinstance({'a':1},Iterator))
print(isinstance({1,2},Iterator))
print(isinstance(f,Iterator))
'''
#结果
True
True
True
True
True
True
False
False
False
False
False
True
'''

迭代对象和迭代器数据类型

常用的数据类型,都是可迭代对象,但是未使用iter()方法之前,默认只有文件是迭代器。

总结:

  • 可迭代对象:只有__iter__方法,执行该方法得到的迭代器对象
  • 迭代器:有__iter____next__()方法

注:

  • 对于迭代器对象来说,执行__iter__方法,得到的结果仍然是它本身
  • python中for循环的实现本质上就是通过将所有的对象内部转为迭代器之后进行迭代循环的,for循环内部处理了StopIteration异常。
  • for迭代 == while + try...except...(异常处理方式)

5. 迭代器的优缺点

优点:

  • 提供了一种不依赖下标的迭代方式
  • 就跌迭代器本身来说,更节省内存
l=[10000,2,3,4,5]

i=iter(l)

print(i)
print(next(i))
'''
<list_iterator object at 0x00000000006F42E8>
10000
'''

next()方法取值

缺点:

  • 无法获取迭代器对象的长度
  • 不如序列类型取值灵活,是一次性的,只能往后取值,不能往前退
l=[10000,2,3,4,5]

i=iter(l)

print(i)
print(next(i))
'''
<list_iterator object at 0x00000000006F42E8>
10000
'''

如过河的卒,只能前进不能后退

PS:

product = ["Mac pro","iphone","iWatch"]
for index,item in enumerate(product,1):
print(index,item)
'''
1 Mac pro
2 iphone
3 iWatch
'''

枚举函数enumerate(),实际也是迭代器

五、函数之生成器

定义: 生成器函数:只要函数体包含yield关键字,该函数就是生成器函数

1. 生成器就是迭代器

def foo():
print('first')
yield 1
print('second')
yield 2
print('third')
yield 3
print('fourth')
yield 4
print('fifth') g=foo()
for i in g:
print(i)

生成器yield关键字

def foo():
print('first')
yield 1
print('second')
yield 2
print('third')
yield 3
print('fourth')
yield 4
print('fifth') g=foo() # 此时g就是一个迭代器
print(g) # g的内存地址
for i in g:
print(i)
print(next(g)) # 触发迭代器g的执行,进而触发函数的执行
'''
# 结果
<generator object foo at 0x00000000006E1150>
first
1
second
2
third
3
fourth
4
fifth
Traceback (most recent call last):
File "E:/YQLFC/study/day04/day4/生成器.py", line 38, in <module>
print(next(g)) #触发迭代器g的执行,进而触发函数的执行
StopIteration
'''

生成器就是迭代器

2.生成器的调用

def counter(n):
print('start...')
i=0
while i < n:
yield i
i+=1
print('end...') g=counter(3)
print(g)
print(next(g))
print(next(g))
print(next(g))
print(next(g))
'''
# 结果
<generator object counter at 0x0000000000B51150>
start...
0
1
2
end...
Traceback (most recent call last):
File "E:/YQLFC/study/day04/day4/生成器.py", line 84, in <module>
print(next(g))
StopIteration
'''

简单调用

总结:
yield的功能:

  1. 相当于为函数封装好__iter__和__next__方法
  2. return只能返回一次值,函数就终止了,而yield能返回多次值,每次返回都会将函数暂停,下一次next会从上一次暂停的位置继续执行

3. yield程序实例(tail -f a.txt | grep 'python' 类功能python程序版)

#tail -f a.txt | grep 'python' 类功能python程序版

# def tail(filepath):
# '''
# tail功能
# :param filepath: 文件路径
# :return: 相当于return文件最后一行,后等待文件输入
# '''
# with open(filepath,encoding='utf-8') as f:
# '''
# seek(offset,whence=0)
# offset:开始的偏移量,也就是代表需要移动偏移的字节数
# whence:给offset参数一个定义,表示要从哪个位置开始偏移;0代表从文件开头开始算起,
# 1代表从当前位置开始算起,2代表从文件末尾算起。默认为0
# '''
# f.seek(0,2)
# while True:
# line=f.readline().strip()
# if line:
# yield line
# else:
# time.sleep(0.2)
#
# #相当于return文件最后一行,后继续等待next下次调用
# t=tail('a.txt')
#
# # 测试tail功能是否有效
# # for line in t:
# # print(line)
#
# def grep(pattern,lines):
# '''
# grep 功能实现
# :param pattern: 校验关键字
# :param lines: 要校验的行
# :return: 相当于return符合检验的行,后等待行继续调用输入
# '''
# for line in lines:
# if pattern in line:
# yield line
#
#
# g=grep('python',tail('a.txt')) #此处的tail('a.txt')是一个迭代器
# #返回内存地址
# print(g)
#
# #迭代输出
# for i in g:
# print(i)

类tail -f a.txt | grep 'python'功能

 4.  协程函数(生成器,yield关键字的另一种用法)

注:

  • 协程函数可以以一个元组的形式send数据
  • 可以在外部获取协程函数的返回值

4.1 协程函数的实现

协程函数的运行流程如下图所示:

Python之路【第五篇】: 函数、闭包、装饰器、迭代器、生成器

#!/usr/bin/env python
#-*- coding:utf-8 -*- # 协程函数第一次调用时需要进行初始化,next(g)或者g.send(None)方式都可以,建议使用装饰器的方式进行初始化协程函数
def deco(func):
def wrapper(*args,**kwargs):
res = func(*args,**kwargs)
next(res)
return res
return wrapper @deco
def eater(name):
print('%s reay to eat'%name)
food_li = []
while True:
food = yield food_li # 此处food_li为函数的返回值,可以在函数外部获取
food_li.append(food)
print("%s start to eat %s" %(name,food)) g = eater('shuke')
# next(g) = g.send(None) # 第一次调用必须先进行初始化,可以使用此种方式进行初始化也可以使用装饰器的方式。
print(g.send('香蕉'))
print(g.send('樱桃'))
res = g.send('西瓜')
print(res) '''
# 结果
shuke reay to eat
shuke start to eat 香蕉
['香蕉']
shuke start to eat 樱桃
['香蕉', '樱桃']
shuke start to eat 西瓜
['香蕉', '樱桃', '西瓜']
'''

使用yield关键字实现协程函数

4.2 yield关键字的表达式形式

应用: 使用yield表达式形式模拟"grep -rl 'python' /root"命令

#grep -rl 'python' /root

import os

def init(func):
def wrapper(*args,**kwargs):
res=func(*args,**kwargs)
next(res)
return res
return wrapper @init # 装饰器:初始化函数
def search(target):
while True:
search_path=yield
g=os.walk(search_path) # 遍历目录
for par_dir,_,files in g: # 用_丢弃子目录
for file in files:
file_abs_path=r'%s\%s' %(par_dir,file) # 拼接文件路径file_abs_path
# print(file_abs_path)
target.send(file_abs_path) # 遍历文件路径并send给opener函数 @init
def opener(target):
while True:
file_abs_path=yield
# print('opener func==>',file_abs_path)
with open(file_abs_path,encoding='utf-8') as f:
target.send((file_abs_path,f)) # send文件路径及文件对象f,以元组的形式send数据 @init
def cat(target): # target为grep函数对象
while True:
file_abs_path,f=yield #(file_abs_path,f)
for line in f:
tag=target.send((file_abs_path,line)) # send文件路径及文件行内容line,以元组的形式send数据
if tag:
break
@init
def grep(target,pattern): # target为printer函数对象,pattern为匹配的字符串'python'
tag=False
while True:
file_abs_path,line=yield tag
tag=False
if pattern in line:
tag=True
target.send(file_abs_path) @init
def printer(): # 输出匹配成功的文件路径
while True:
file_abs_path=yield
print(file_abs_path) x=r'E:\YQLFC\study\day05\day5\a' # 查找的目录 g=search(opener(cat(grep(printer(),'python'))))
print(g)
g.send(x) '''
#结果
<generator object search at 0x0000000000D514C0>
E:\YQLFC\study\day05\day5\a\a.txt
E:\YQLFC\study\day05\day5\a\b\b.txt
E:\YQLFC\study\day05\day5\a\b\c\d\d.txt
'''

grep -rl 'python' /root

六. 面向过程的程序设计思想

面向过程的程序设计的特点: 是一种流水线式的编程思路,是机械式的。

优点:
  • 程序的结构清晰,可以把复杂的问题简单
缺点:
  • 扩展性差

应用场景: linux内核,git,httpd...