闭包与装饰器
闭包:
在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。
闭包就是在函数内再定义一个内部函数,用内部函数来保存外部函数的的变量,其变量不会随着外部函数调用完而销毁
简单闭包的示例代码:
定义一个外部函数
def func_out(num1):
定义一个内部函数
def func_inner(num2):
内部函数使用了外部函数的变量(num1)
result = num1 + num2
print("结果是:", result)
外部函数返回了内部函数,这里返回的内部函数就是闭包
return func_inner
创建闭包实例
f = func_out(1)
执行闭包
f(2)
f(3)
运行结果:
结果是: 3
结果是: 4
通过上面的输出结果可以看出闭包保存了外部函数内的变量num1,每次执行闭包都是在num1 = 1 基础上进行计算
例2:
外部函数
def config_name(name):
内部函数
def say_info(info):
print(name + ": " + info)
return say_info
tom = config_name("Tom")
tom("你好!")
tom("你好, 在吗?")
jerry = config_name("jerry")
jerry("不在, 不和玩!")
运行结果:
Tom: 你好!
Tom: 你好, 在吗?
jerry: 不在, 不和玩!
修改闭包内的外部变量:
修改闭包内使用的外部函数变量使用 nonlocal 关键字来完成。
定义一个外部函数
def func_out(num1):
定义一个内部函数
def func_inner(num2):
这里本意想要修改外部num1的值,实际上是在内部函数定义了一个局部变量num1
nonlocal num1 告诉解释器,此处使用的是 外部变量a
修改外部变量num1
num1 = 10
内部函数使用了外部函数的变量(num1)
result = num1 + num2
print("结果是:", result)
print(num1)
func_inner(1)
print(num1)
外部函数返回了内部函数,这里返回的内部函数就是闭包
return func_inner
创建闭包实例
f = func_out(1)
执行闭包
f(2)
装饰器:
装饰器的目的是为了实现在原本函数之上添加功能(代码),本身不影响源代码。其目的是真正作为装饰而存在。
装饰器的功能特点:
不修改已有函数的源代码
不修改已有函数的调用方式
给已有函数增加额外的功能
装饰器本质上就是一个闭包函数
示例:
def check(fn):
def inner():
print(“请先登录….”)
fn()
return inner
def comment():
print(“发表评论”)
comment = check(comment)
comment()
打印结果:
请先登录....
发表评论
可以看到,此处先定义了一个comment函数(发表评论),但是在添加功能(打印:请先登录)时,并没有直接在原有函数上添加代码,而是重新创建了一个函数,并在其内部函数中添加代码并调用源函数,以实现装饰器。并且由于装饰器其调用方式不能改变,所以最后再check调用时,把函数check赋给了comment,此时的comment其实就是check调用原来comment的新函数。把外部函数保存(调用)到内部函数本质上也是属于闭包。
装饰器本身就是套用函数,在原函数上面套用一个内部函数,也就是在这里添加代码实现装饰,再在内部函数外面套用一个外部函数,实现调用(调用最里面的函数,也是装饰器需要装饰的那个函数)。
装饰器的语法糖写法:
Python给提供了一个装饰函数更加简单的写法,那就是语法糖,语法糖的书写格式是: @装饰器名字,通过语法糖的方式也可以完成对已有函数的装饰
如果有多个函数都需要添加登录验证的功能,每次都需要编写func = check(func)这样代码对已有函数进行装饰,这种做法还是比较麻烦。
例:
def check(fn):
def inner():
print("请先登录....")
fn()
return inner
@check @check 等价于 comment = check(comment)
def comment():
print("发表评论")
comment()
装饰器的使用:
1.通过装饰器给原函数传递参数
如果在添加装饰器时,原函数需要传递参数,则装饰器也需要和原函数一样传递参数
例:
def zsq_sum(s):
def inner(a,b):
print('正在执行计算')
s(a,b)
return inner
@zsq_sum
def sum_(a,b):
result = a + b
print(result)
sum_(1,2)
原函数在此处需要传递两个函数来实现相加,但添加装饰器以后,装饰器内部函数也需要传递参数,因执行@zsq_sum(sum_=zsq_sum(sum_))后,此时原函数sum_ 等于s(),则此时真正在执行代码传递参数时,此时的参数是传递到inner函数中的s中,然后再传递到sum_。
2.装饰带有返回值的函数
以上面的代码为例,在result变量执行后 sum_并没有真正返回函数值,而是print打印了其函数值,下面的代码是将其原本的print改为返回函数值,使外部变量zsq_sum在调用时能直接拿到该值:
def zsq_sum(s):
def inner(a,b):
print('正在执行计算')
result = s(a,b)
return result
return inner
@zsq_sum
def sum_(a,b):
result = a + b
return result
result = sum_(1,2)
print(result)
此处sum_返回了result,则sum_函数返回值为result,inner中此处把s函数(也就是sum_)赋给了变量result,然后返回变量值(这里直接打印函数不会得到返回值,只能返回其内存地址信息,所以需要赋值)。
3.装饰带有不定长参数的函数
def zsq_sum(s):
def inner(*args,**kwargs):
print('正在执行计算')
result = s(*args,**kwargs)
return result
return inner
@zsq_sum
def sum_(*args,**kwargs):
result = 0
for i in args:
result += i
for i in kwargs.values:
result += i
return result
result = sum_(1,2)
print(result)
这里用args和kwargs接受位置参数和非位置参数(字典参数),然后for读取里面里面的值,字典需要跟上.values来读取值,否则为读取字典名。最内部的函数sum_和inner都需要传递参数(因其本身是从inner进去的)。
4.多个装饰器的使用示例代码
def make_div(func):
"""对被装饰的函数的返回值 div标签"""
def inner(*args, **kwargs):
return "<div>" + func() + "</div>"
return inner
def make_p(func):
"""对被装饰的函数的返回值 p标签"""
def inner(*args, **kwargs):
return "<p>" + func() + "</p>"
return inner
装饰过程: 1 content = make_p(content) 2 content = make_div(content)
content = make_div(make_p(content))
@make_div
@make_p
def content():
return "人生苦短"
result = content()
print(result)
代码说明:
多个装饰器的装饰过程是: 离函数最近的装饰器先装饰,然后外面的装饰器再进行装饰,由内到外的装饰过程
5.带有参数的装饰器介绍
带有参数的装饰器就是使用装饰器装饰函数的时候可以传入指定参数,语法格式: @装饰器(参数,...)
装饰器只能接收一个参数,并且还是函数类型。所以必须在装饰器外面再包裹上一个函数,让最外面的函数接收参数,返回的是装饰器,因为@符号后面必须是装饰器实例。
添加输出日志的功能
def logging(flag):
def decorator(fn):
def inner(num1, num2):
if flag == "+":
print("--正在努力加法计算--")
elif flag == "-":
print("--正在努力减法计算--")
result = fn(num1, num2)
return result
return inner
返回装饰器
return decorator
使用装饰器装饰函数
@logging("+")
def add(a, b):
result = a + b
return result
@logging("-")
def sub(a, b):
result = a - b
return result
result = add(1, 2)
print(result)
result = sub(1, 2)
print(result)
6.类装饰器
类装饰器的介绍
装饰器还有一种特殊的用法就是类装饰器,就是通过定义一个类来装饰函数。
类装饰器示例代码:
class Check(object):
def __init__(self, fn):
初始化操作在此完成
self.__fn = fn
实现__call__方法,表示对象是一个可调用对象,可以像调用函数一样进行调用。
def __call__(self, *args, **kwargs):
添加装饰功能
print("请先登陆...")
self.__fn()
@Check
def comment():
print("发表评论")
comment()
@Check 等价于 comment = Check(comment), 所以需要提供一个init方法,并多增加一个fn参数。
要想类的实例对象能够像函数一样调用,需要在类里面使用call方法,把类的实例变成可调用对象(callable),也就是说可以像调用函数一样进行调用。
在call方法里进行对fn函数的装饰,可以添加额外的功能。
执行结果:
请先登陆...
发表评论
迭代器:
迭代器指的是迭代取值的工具,迭代是一个重复的过程,每次重复都是基于上一次的结果而继续的,单纯的重复并非迭代。
为何要有迭代器
迭代器是用来迭代取值的工具,而涉及到把多个值循环取出来的类型
有:列表、字符串、元组、字典、集合、打开文件
l=['egon','liu','alex']
i=0
while i < len(l):
print(l[i])
i+=1
上述迭代取值的方式只适用于有索引的数据类型:列表、字符串、元组
为了解决基于索引迭代器取值的局限性
python必须提供一种能够不依赖于索引的取值方式,这就是迭代器
如何用迭代器
可迭代的对象:但凡内置有__iter__方法的都称之为可迭代的对象,文件对象本身就是可迭代对象,无需再定义。
s1=''
s1.__iter__()
l=[]
l.__iter__()
.....
测试所有类型的对象皆有iter方法。
调用可迭代对象下的__iter__方法会将其转换成迭代器对象
d={'a':1,'b':2,'c':3}
d_iterator=d.__iter__()
print(d_iterator)
print(d_iterator.__next__()) 结果 1
print(d_iterator.__next__()) 结果 2
print(d_iterator.__next__()) 结果 3
print(d_iterator.__next__()) 抛出异常StopIteration
例子:
while True:
try:
print(d_iterator.__next__())
except StopIteration:
break
print('====>>>>>>') 在一个迭代器取值取干净的情况下,再对其取值娶不到
d_iterator=d.__iter__()
while True:
try:
print(d_iterator.__next__())
except StopIteration:
break
可迭代对象与迭代器对象详解
可迭代对象("可以转换成迭代器的对象"):内置有__iter__方法对象
可迭代对象.__iter__(): 得到迭代器对象
迭代器对象:内置有__next__方法并且内置有__iter__方法的对象
迭代器对象.__next__():得到迭代器的下一个值
迭代器对象.__iter__():得到迭代器的本身,说白了调了跟没调一个样子
dic={'a':1,'b':2,'c':3}
dic_iterator=dic.__iter__()
print(dic_iterator is dic_iterator.__iter__().__iter__().__iter__())
for循环的工作原理:for循环可以称之为叫迭代器循环
d={'a':1,'b':2,'c':3}
1、d.__iter__()得到一个迭代器对象
2、迭代器对象.__next__()拿到一个返回值,然后将该返回值赋值给k
3、循环往复步骤2,直到抛出StopIteration异常for循环会捕捉异常然后结束循环
for k in d:
print(k)
with open('a.txt',mode='rt',encoding='utf-8') as f:
for line in f: f.__iter__()
print(line)
list('hello') 原理同for循环
6、迭代器优缺点总结
6.1 缺点:
I、为序列和非序列类型提供了一种统一的迭代取值方式。
II、惰性计算:迭代器对象表示的是一个数据流,可以只在需要时才去调用next来计算出一个值,就迭代器本身来说,同一时刻在内存中只有一个值,因而可以存放无限大的数据流,而对于其他容器类型,如列表,需要把所有的元素都存放于内存中,受内存大小的限制,可以存放的值的个数是有限的。
6.2 缺点:
I、除非取尽,否则无法获取迭代器的长度
II、只能取下一个值,不能回到开始,更像是‘一次性的’,迭代器产生后的唯一目标就是重复执行next方法直到值取尽,否则就会停留在某个位置,等待下一次调用next;若是要再次迭代同个对象,你只能重新调用iter方法去创建一个新的迭代器对象,如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环能取到值。
生成器:
生成器就是自定义迭代器。
如何得到自定义的迭代器:
在函数内一旦存在yield关键字,调用函数并不会执行函数体代码
会返回一个生成器对象,生成器即自定义的迭代器
def func():
print('第一次')
yield 1
print('第二次')
yield 2
print('第三次')
yield 3
print('第四次')
g=func()
print(g)
生成器就是迭代器
g.__iter__()
g.__next__()
会触发函数体代码的运行,然后遇到yield停下来,将yield后的值
当做本次调用的结果返回
res1=g.__next__()
print(res1)
res2=g.__next__()
print(res2)
res3=g.__next__()
print(res3)
res4=g.__next__()
应用案列
def my_range(start,stop,step=1):
print('start...')
while start < stop:
yield start
start+=step
print('end....')
g=my_range(1,5,2) 1 3
print(next(g))
print(next(g))
print(next(g))
for n in my_range(1,7,2):
print(n)
总结yield:
有了yield关键字,我们就有了一种自定义迭代器的实现方式。yield可以用于返回值,但不同于return,函数一旦遇到return就结束了,而yield可以保存函数的运行状态挂起函数,用来返回多次值
生成式:
# 1、列表生成式
l = [‘alex_dsb’, ‘lxx_dsb’, ‘wxx_dsb’, “xxq_dsb”, ‘egon’]
# new_l=[]
# for name in l:
# if name.endswith(‘dsb’):
# new_l.append(name)
# new_l=[name for name in l if name.endswith('dsb')]
# new_l=[name for name in l]
# print(new_l)
# 把所有小写字母全变成大写
# new_l=[name.upper() for name in l]
# print(new_l)
# 把所有的名字去掉后缀_dsb
# new_l=[name.replace('_dsb','') for name in l]
# print(new_l)
# 2、字典生成式
# keys=['name','age','gender']
# dic={key:None for key in keys}
# print(dic)
# items=[('name','egon'),('age',18),('gender','male')]
# res={k:v for k,v in items if k != 'gender'}
# print(res)
# 3、集合生成式
# keys=['name','age','gender']
# set1={key for key in keys}
# print(set1,type(set1))
# 4、生成器表达式
# g=(i for i in range(10) if i > 3)
# !!!!!!!!!!!强调!!!!!!!!!!!!!!!
# 此刻g内部一个值也没有
# print(g,type(g))
# print(g)
# print(next(g))
# print(next(g))
# print(next(g))
# print(next(g))
# print(next(g))
# print(next(g))
# print(next(g))
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!