闭包函数
1.闭:定义在函数内部的函数
2.包:内部函数引用了外部函数作用域的名字
def outter(): x = 111 def inner(): print(x) return inner res = outter() # res就是inner函数内存地址 def func(): x = 333 res() func()
给函数体传值的第一种方式 传参 def index1(username): print(username)
# 给函数体传参的第二种方式 闭包
def outter(x,y):
# x = 1
# y = 40
def my_max():
if x > y:
return x
return y
return my_max
res1 = outter(1,40) # res就是my_max函数的内存地址
print(res1())
res2 = outter(90,200)
print(res2())
小爬虫 需要导入requests模块
import requests
# 第一个直接给函数传参
url1 = 'https://www.baidu.com'
url2 = '...'
def my_get(url):
response = requests.get(url)
if response.status_code == 200:
print(len(response.text))
my_get(url1)
my_get(url1)
my_get(url1)
my_get('https://www.baidu.com')
my_get('https://www.baidu.com')
my_get('https://www.baidu.com')
# 第二种给函数传参的方式 闭包
def outter(url):
# url = 'https://www.jd.com'
def my_get():
response = requests.get(url)
if response.status_code == 200:
print(len(response.text))
return my_get
my_jd = outter('https://www.jd.com')
my_jd()
my_jd()
my_baidu = outter('https://www.baidu.com')
my_baidu()
my_baidu()
my_baidu()
开放封闭原则
1.不能修改被装饰对象(函数)的源代码(封闭)
2.不能更改被修饰对象(函数)的调用方式,且能达到增加功能的效果(开放)
总结起来就是在不修改源代码的情况下增加新的功能
装饰器
装饰器:
器:就是一个工具
装饰:给被装饰对象添加新的功能
为什么要用装饰器
开放封闭原则:
开放:对扩展开放
封闭:对修改封闭
装饰器(可调用对象)必须遵循的两个原则:
1.不改变被装饰对象源代码
2.不改变被装饰对象(可调用对象)调用方式
def index():
pass
index()
# import time # print(time.time()) # 1562812014.731474 时间戳 当前时间距离1970-1-1 00:00:00相差的秒数 # 1970-1-1 00:00:00是Unix诞生元年 # time.sleep(3) # 让cpu谁三秒 让你的程序暂停三秒 # print('FBI warning!') 1.统计index函数执行的时间 import time def index(): time.sleep(3) print('澳门最大线上赌场开业啦 性感tank在线发牌!') start = time.time() index() end = time.time() print('index run time:%s'%(end-start))
初衷(由来):想要在不改变函数源代码且不改变函数调用方式的情况下给函数添加新功能
开放封闭原则: # 对扩展开放,对修改封闭
首先要申明一点: 装饰器真的不难,真的不难,你只要跟着我理解一遍,以后就都不是问题了(忘了你就再看一遍嘛) ,装饰器只是闭包函数的一种扩展应用。
装饰器推导过程
现有一个需求,给现有的shopping 功能扩展一下,自动判断其是否已经登录,未登录先登录(不改源码与调用方式)
def shopping(): print("我要开始购物啦!") pass
不让改源码又不让改调用方式,那咋整呢?(反正我想了半天是想不出来)
那就一起来头脑风暴一下
先抛开调用方式,我们可以在它调用前后加上自己的逻辑代码,然后封装成函数,通过调用这个函数实现添加功能的目的
def check_login(): if is_login: shopping() else: login() # 调用login 函数,登录,登陆结束后会接着执行shopping 函数 shopping()
如果尝试运用昨天的知识点,函数名可以被当做变量一样赋值传递呢?就是说我把 shopping = check_login ,那是不是说我再 shopping() 执行的就是 check_login() 方法了呢?
is_login = False def shopping(): print("我要开始购物啦!") pass def check_login(): global is_login if is_login: shopping() else: print("登录成功了") # 调用login 函数,登录,登陆结束后会接着执行shopping 函数 is_login = True # 这里写的是 = 赋值操作,局部命名空间会新创建一个变量is_login 而我们要用的是全局的,所以在上方要加上 global is_login shopping() shopping = check_login shopping()
一运行就发现编译器报错了,这样一写原来 check_login() 里面的 shopping 函数 也被顶掉了, 执行的还是 check_login 函数本身 ,它会无限调用自身,然后。。。。
那我可不可以利用 # 名称空间查找顺序在函数定义阶段就已经固定了 的特性,再利用 # 函数名可被当做函数的参数被传递 的特性把 函数名 shopping 作为参数传入进去,然后shopping()的时候调用的是传进去的shopping函数呢?
is_login = False def shopping(): print("我要开始购物啦!") pass def check_login(shopping): global is_login if is_login: shopping() else: print("登录成功了") # 调用login 函数,登录,登陆结束后会接着执行shopping 函数 is_login = True # 这里写的是 = 赋值操作,局部命名空间会新创建一个变量is_login 而我们要用的是全局的,所以在上方要加上 global is_login shopping() shopping = check_login(shopping) shopping() # 登录成功了 # 我要开始购物啦! # Traceback (most recent call last): # File "E:/PyCharm 2019.1.3/ProjectFile/day010/day011/博客代码整理草稿.py", line 20, in <module> # shopping() # TypeError: 'NoneType' object is not callable # tips: 上面的那个报错位置可能会在前两个输出的前面,但这不重要 代码
一运行发现还是报错了,但好像又有点样子了,一经排查,原来在 shopping = check_login(shopping) 的时候就完成了我们的扩展功能。。。报错的是下面那句 shopping() ,前面那句执行了 check_login 函数 ,而 check_login 函数 没有返回值,那么 shopping 变量 接收到的其实是 None ,再执行 shopping() 这就是那个报错的原因了
那我可不可以利用 # 函数在定义阶段只会检查语法,不会执行内部代码 和 # 函数名可以被当做函数的返回值的特性 的特性把 check_login 函数 里面的代码再给它封装一个函数然后返回这个函数名呢?再利用 # 函数名加括号可以调用函数 的特点,要用的时候再给接收的变量加上括号不就可以调用了吗?
is_login = False def shopping(): print("我要开始购物啦!") pass def check_login(shopping): def inner(): global is_login if is_login: shopping() else: print("登录成功了") # 调用login 函数,登录,登陆结束后会接着执行shopping 函数 is_login = True # 这里写的是 = 赋值操作,局部命名空间会新创建一个变量is_login 而我们要用的是全局的,所以在上方要加上 global is_login shopping() return inner shopping = check_login(shopping) shopping() # 登录成功了 # 我要开始购物啦! 代码
经过上述那么一波猛如虎的操作,发现,耶?我好像达到了要求,既没有改变原函数,也没有改变它的调用方式???
那...我要给现有的 pay 功能也同样扩展一下呢?再...?再写一遍?我不!作为一个有追求的程序员,我觉得我的代码还可以抢救一下(不然你让我再给其他功能也同样加上这个登录验证...?那如果有几十个...?想想还是花点脑子写个通用的吧,最起码后面用起来可以省时省事呀,以后也可以模仿着写)
def pay(): print("我要结账啦!") pass
既然先给 pay 函数 也扩展,那我上面的写法里面肯定就不能直接是 shopping() 了,那我再利用一下 # 函数名可以被当做函数的参数被传递 的特性,把.....?等等,函数定义时的形参好像相当于是一个变量我好像直接把上面的 shopping = check_login(shopping) 改成 pay = check_login(pay) 然后 pay() 不就搞定了?一试还真的是这样....
不过啊,这里的shopping 和 pay 函数好像都是没有参数也没有返回值的,那...?要被扩展的函数有参数,或者有返回值呢?那函数的参数又有好几个呢?总不可能每个函数的参数个数都一样吧?这该如何是好?
要可以有返回值,那我调用完被扩展的函数用一个变量接收它的返回值不就行了,再return 出来,哎... 这个简单
那可以有任意个参数呢?emmm? 等等,任意个参数,这好像和可变长参数的应用场景差不多啊,我可以用 # * 接收多余的位置参数, ** 接收多余的关键字参数 ,嘿,这就不管你来几个参数我都可以接收了,那我再通过前面学的打散机制,用 # * 打散容器对象,拆成若干个位置参数, ** 打散字典对象,拆成若干个关键字参数 ,不就完成了参数的传递了?哎,作为一个处女座, check_login 函数 里面接收到的函数名和被执行的函数名怎么能还是shopping 呢?改一下改一下,规范点。
is_login = False def say_hi(username): return 'hello, {}'.format(username) def hello_world(): print('Hello world!') def check_login(func): # 被扩展的函数总不能都叫shopping 吧?取个func 统一代表被扩展的函数吧 # 肯定是调用被扩展函数的时候才根据情况传参数的嘛,所以这里check_login和inner 的参数就得这么写 def inner(*args, **kwargs): # 利用可变长参数接收不定个数的参数(可变长参数的标准写法哦) global is_login if is_login: res = func(*args, **kwargs) # 再利用* ** 的打散机制,将inner 接收到的参数打散 # 用一个变量 res 来接收被扩展函数的返回值 else: print("登录成功了") is_login = True res = func(*args, **kwargs) return res # 再把这个被扩展函数的返回值返回回去 return inner # 返回给外界一个函数名,这样外界就可以拿到这个函数名加括号直接调用了 say_hi = check_login(say_hi) print(say_hi('jason')) # 登录成功了 # hello, jason hello_world = check_login(hello_world) hello_world() # Hello world! 代码
哇塞,完美啊,既可以给有参数的函数调用,又可以给没参数的函数调用,既可以给有返回值的函数调用,又可以给没有返回值的函数调用!NB(到这里,一个简单的装饰器就算是写好了)
但是啊,这个 hello_world = check_login(hello_world) 好像有点多余啊...我不想每次都要写这么一句,哎,python提供的装饰器语法糖了解一下
is_login = False def check_login(func): # 被扩展的函数总不能都叫shopping 吧?取个func 统一代表被扩展的函数吧 # 肯定是调用被扩展函数的时候才根据情况传参数的嘛,所以这里check_login和inner 的参数就得这么写 def inner(*args, **kwargs): # 利用可变长参数接收不定个数的参数(可变长参数的标准写法哦) global is_login if is_login: res = func(*args, **kwargs) # 再利用* ** 的打散机制,将inner 接收到的参数打散 # 用一个变量 res 来接收被扩展函数的返回值 else: print("登录成功了") is_login = True res = func(*args, **kwargs) return res # 再把这个被扩展函数的返回值返回回去 return inner # 返回给外界一个函数名,这样外界就可以拿到这个函数名加括号直接调用了 @check_login # 等价于 say_hi = check_login(say_hi) def say_hi(username): return 'hello, {}'.format(username) @check_login def hello_world(): print('Hello world!') # say_hi = check_login(say_hi) print(say_hi('jason')) # 登录成功了 # hello, jason # hello_world = check_login(hello_world) hello_world() # Hello world! 装饰器语法糖代码
到了这里,插播一小段装饰器语法糖啊,各位观众姥爷不介意吧?咱们装饰器语法糖之后再见
装饰器语法糖
工作原理: # 装饰器语法糖会自动将下面的可调用对象的名字(函数)当做参数直接传入 @后所跟函数名并自动调用
注意点: # 装饰器语法糖在书写的时候应该与被装饰对象紧紧挨着,中间不能有空行 --> 也就意味着被扩展(装饰)函数要写在装饰器函数的后面,不然装饰器还没定义,你用啥嘞?
案例的话上面的代码就是咯,多多用装饰的语法糖可以让代码的可读性更强哦
装饰器推导过程
好,下面我们接着上面的话题继续扯。
这个时候我打印一下 hello_world,我天?怎么是inner ?那我要看下 hello_world 的注释呢?打印一下,耶?也是inner 的,那咋整咧?装饰器修复技术了解一下?
is_login = False def check_login(func): # inner的注释(不给inner函数写函数注释的时候,help(inner) 返回的是这句话,当然这里的inner并没有返回给外界,在全局是获取不到的) def inner(*args, **kwargs): ''' inner的函数注释 :param args: 任意个数的位置参数会被组成一个元组接收 :param kwargs: 任意个数的关键字参数会被组成一个元组接收 :return: 被装饰的函数返回值是什么,这里的返回值就是什么 ''' global is_login if is_login: res = func(*args, **kwargs) # 再利用* ** 的打散机制,将inner 接收到的参数打散 # 用一个变量 res 来接收被扩展函数的返回值 else: print("登录成功了") is_login = True res = func(*args, **kwargs) return res # 再把这个被扩展函数的返回值返回回去 return inner # 返回给外界一个函数名,这样外界就可以拿到这个函数名加括号直接调用了 @check_login # 等价于 say_hi = check_login(say_hi) def say_hi(username): return 'hello, {}'.format(username) @check_login def hello_world(): print('Hello world!') print(hello_world) # <function check_login.<locals>.inner at 0x000002651E63BAE8> print(help(hello_world)) # Help on function inner in module __main__: # # inner(*args, **kwargs) # inner的函数注释 # :param args: 任意个数的位置参数会被组成一个元组接收 # :param kwargs: 任意个数的关键字参数会被组成一个元组接收 # :return: 被装饰的函数返回值是什么,这里的返回值就是什么 # # None 修复前
from functools import wraps is_login = False def check_login(func): # inner的注释(不给inner函数写函数注释的时候,help(inner) 返回的是这句话,当然这里的inner并没有返回给外界,在全局是获取不到的) @wraps(func) # 不要忘了导最上面的包,这个语法糖一定要在最内层函数的上一行,然后要指定参数(被装饰方法) def inner(*args, **kwargs): ''' inner的函数注释 :param args: 任意个数的位置参数会被组成一个元组接收 :param kwargs: 任意个数的关键字参数会被组成一个元组接收 :return: 被装饰的函数返回值是什么,这里的返回值就是什么 ''' global is_login if is_login: res = func(*args, **kwargs) # 再利用* ** 的打散机制,将inner 接收到的参数打散 # 用一个变量 res 来接收被扩展函数的返回值 else: print("登录成功了") is_login = True res = func(*args, **kwargs) return res # 再把这个被扩展函数的返回值返回回去 return inner # 返回给外界一个函数名,这样外界就可以拿到这个函数名加括号直接调用了 @check_login # 等价于 say_hi = check_login(say_hi) def say_hi(username): return 'hello, {}'.format(username) @check_login # 测试注释,如果 不写hello_world函数的注释的话,返回的还是inner 函数上方的那行注释(在inner 函数有些函数注释的情况下),这一句注释并不会返回 def hello_world(): ''' hello_world 函数的注释 :return: 木得返回值 ''' print('Hello world!') print(hello_world) # <function hello_world at 0x000001EAA7A50378> print(help(hello_world)) # Help on function hello_world in module __main__: # # hello_world() # hello_world 函数的注释 # :return: 木得返回值 # # None 修复后
不带参数的装饰器模版
from functools import wraps # 要用到 wraps 装饰器修复技术,就不要忘了导入这个包 def outter(func): # 这个outter 函数的名字最好取实际用途的名字,比如统计函数运行时间,就可以改成statistical_execution_time @wraps(func) # 加上这句让被装饰函数再被 print(函数名) 的时候可以打印出他自己的内存地址, print(help(函数名)) 的时候可以打印出他自己的注释 def inner(*args, **kwargs): # 这个inner 函数的名字就无所谓啦,没有太大的意义,或者你可以根据你的理解给他取个名字 # 这里写被装饰函数执行前的操作 res = func(*args, **kwargs) # 这里写被装饰函数执行后的操做 return res # 这里不要忘了return 被装饰函数的返回值 return inner # 注意,这里的inner 不能加括号 # 最后一把,在要被他装饰的函数定义位置的上一行加上这个语法糖 --> @outter 简单装饰器模板
咳,编不下去了,这里在扩展一下带参数的装饰器,分析了一下,装饰内部的 inner 函数 参数是可变长度参数,你可以选择在 *args 前面加个位置参数,传入参数,但那就意味着你调用被装饰函数的时候,要多传一个参数,也就意味着调用方式变了,他不再是装饰器了。
那我给最外层的函数多加一个参数呢?
from functools import wraps # 要用到 wraps 装饰器修复技术,就不要忘了导入这个包 def outter(x,func): @wraps(func) def inner(*args, **kwargs): # 这里写被装饰函数执行前的操作 res = func(*args, **kwargs) # 这里写被装饰函数执行后的操做 return res # 这里不要忘了return 被装饰函数的返回值 return inner # 注意,这里的inner 不能加括号 # @outter(1, hello) # 会报错,因为程序执行到这里 hello 函数还没有定义 def hello(): print("hello") hello = outter(1, hello) # 这样写可读性不太好,当然是能用语法糖就语法糖啦 hello() # hello 给装饰器传参(请勿模仿)
带参数的装饰器模板
from functools import wraps # 要用到 wraps 装饰器修复技术,就不要忘了导入这个包 def outs(x): # 其实这里已经可以随便你指定多少个参数了(inner 函数里面可以获取到用了,这里只演示可以传) def outter(func): @wraps(func) def inner(*args, **kwargs): # 这里写被装饰函数执行前的操作 res = func(*args, **kwargs) # 这里写被装饰函数执行后的操做 return res # 这里不要忘了return 被装饰函数的返回值 return inner # 注意,这里的inner 不能加括号 return outter # 这里这个return 函数名 也不要忘了,记住一点,函数名后面千万不要加括号!函数名后面千万不要加括号!函数名后面千万不要加括号! # 至此,带参数版的装饰器差不多就写好了,outs函数里的 x 根据情况换成需要的参数(如果你不需要参数,那你写简单版的不好吗?) @outs(1) # 这里的outs 要加括号!直接执行 outs 函数,然后返回 outter函数内存地址,传入hello 函数名(对象),装饰 # --> outs() -> hello = outter(hello) def hello(): print("hello") # 上面装饰器语法糖的写法等同于: # outter = outs(1) # hello = outter(hello) hello() # hello 带参数的装饰器模板
emmm,到这里就差不多了,完事儿,收工。
咳,申明一点啊,装饰器可不是我推导出来的,写上面那一长串的知识点呢,主要是为了真正了解装饰器,并且复习一下前面的知识点嘛
下面通过一个案例加深一下对装饰器的理解(多层装饰器)
def outter1(func1): print('语法糖加载outter1') def wrapper1(*args, **kwargs): print('被装饰函数函数名加括号调用,执行到了wrapper1') res1 = func1(*args, **kwargs) return res1 return wrapper1 def outter2(func2): print('语法糖加载outter2') def wrapper2(*args, **kwargs): print('被装饰函数函数名加括号调用,执行到了wrapper2') res2 = func2(*args, **kwargs) return res2 return wrapper2 def outter3(func3): print('语法糖加载outter3') def wrapper3(*args, **kwargs): print('被装饰函数函数名加括号调用,执行到了wrapper3') res3 = func3(*args, **kwargs) return res3 return wrapper3 @outter1 # index = outter1(wapper2) # 完成装饰 @outter2 # wrapper2 = outter2(wrapper3) @outter3 # wrapper3 = outter3(最原始的index函数内存地址) # 语法糖加载outter3 # 只要用语法糖装饰到函数上就会返回 # 语法糖加载outter2 # 语法糖加载outter1 def index(): print('from index') index() # 被装饰函数函数名加括号调用,执行到了wrapper1 # 被装饰函数函数名加括号调用,执行到了wrapper2 # 被装饰函数函数名加括号调用,执行到了wrapper3 # from index 装饰器巩固案例