python第十二课---闭包函数,装饰器器原理等

昨日内容回顾

  • 函数参数

    """
    短的 简单的靠前
    长的 复杂的靠后
    同一个形参在调用的过程中不能多次赋值
    """
    位置参数
    	位置形参
        	函数定义阶段括号内依次填写的变量名
     	位置实参
        	函数调用阶段括号内依次填写的数据值
    	基本规律是按照位置顺序一一对应传值
    	'''如果想打破位置顺序可以采用关键字传参'''
    
    默认参数
    	默认形参
    	函数定义阶段就直接给形参赋值
    		不传则使用默认的,传了则使用传了的
    
    可变长参数
    	可变长形参
      *号 接收多余的位置参数组织成元组赋值给符号后面的变量名
      	*args
    
      	**号 接收多余的关键字参数组织成字典的形式赋值给符号后面的变量名
      	**kwargs
      
     	可变长实参
      	*号 类似于for循环 将整个循环取值的整体结果 一次性传给函数
      	**号 将字典的键值对打散成关键字参数的形式 传给函数
            
    命名关键字参数
    	在函数定义阶段在*args后面填写的默认参数
     	def index(name,age,*args,gender='male',**kwargs):pass
    
  • 名称空间

  用来存储变量名与数据值绑定的关系的地方(也可以直接看成是存变量名)
  内置名称空间
  	解释器启动创建 结束销毁 
  全局名称空间
  	py文件运行创建 结束销毁
name = 'jason'             # name
if True:
	age = 18               # true
	while True:
		hobby = 'read'     # hobby   只要不在函数体代码中的被赋值的变量名都在全局空间中
  	def index():
          pass
   	class MyClass():
          pass
  局部名称空间
  	函数体代码运行创建 结束销毁
  • 名字的查找顺序

    先判断自己在哪个名称空间
    	局部名称空间 >>> 全局名称空间 >>> 内置名称空间
    局部名称空间相关事项
    	1.相互独立的局部名称空间无法彼此交互
     	2.局部名称空间嵌套 大致顺序是由内而外
    	ps:函数在定义阶段名字的查找顺序就已经固定死了
    		  name = 'jason'
            def index():
                name = 'kevin'
                def inner():
                    print(name)
                    name = 'tony'
                inner()
            index()
    ps:名字的查找顺序可以打破 想怎么找就怎么找
    

今日内容概要

  • global与nonlocal
  • 函数名的多种用法
  • 闭包函数
  • 装饰器简介
  • 无参装饰器
  • 有参装饰器
  • 装饰器模板
  • 装饰器语法糖及修复技术
  • 多层装饰器

今日内容详细

global与nonlocal

global作用: 局部名称空间修改全局名称空间
nonlocal作用: 局部内层名称空间修改局部外层名称空间

money = 666
def index():
    money = 123
index()
print(money)
666

如果想实现在:局部名称空间直接修改全局名称空间中的数据,global代码实现如下:
money = 666
def index():
    global money    # 加了一个global告诉程序,一旦涉及到global后面的变量名money的绑定关系,就不会在局部空间里面再创建新的了,而是直接改全局名称空间里面money对应的绑定值。
    money = 123
index()             # 调用函数,执行函数体代码,由于加了global所以money=3直接改了全部名称空间里面的money=6
print(money)        # 函数体代码执行完了后,此时print打印的全局名称空间里面的money已经被改成123了
123
#
#
def index():
    name = 'jason'        # name变量名存到index的局部名称空间,指向'jason'
    def inner():
        name = 'kevin'    # 这时候局部名称空间里面又套了inner一个局部名称空间里面也保存了一个变量名name,指向'kevin'
    inner()
    print(name)           # 这个时候,在index的局部名称空间里,从index的局部名称空间里面找变量名name,所以找到的值为'jason'
index()
jason

如果想实现在:内层局部名称空间直接修改外层局部名称空间中的数据,nonlocal代码实现如下:
def index():
    name = 'jason'
    def inner():
        nonlocal name
        # 告诉程序name在后续赋值时,变量名不会放在内层局部空间,
        # 而是放到它的外层局部名称空间去,如果外层原来有name的绑定关系,
        # 解除原来的关系,用内层赋值的关系。
        name = 'kevin'    # 这时候已经通过内层name的绑定改了外层name的绑定关系了
    inner()              # 所以inner函数体代码执行完了后,外部name绑定关系已经变调了
    print(name)          # 在index的内部名称空间找name变量名对应的值,kevin
index()
kevin



.
.
.
.
.
.

函数名的多种用法 重要!!!

函数名与普通的变量名差不多,都是绑定的一块内存地址!!!
普通的变量名绑定的内存地址存放的是具体的数据值
函数名绑定的内存地址里面存放的不是数据值,而是一些代码!!!
如果想让这些代码执行的话,必须函数名后面加括号,这时候执行的,不单单是找到函数名所绑定的函数体代码所在的内存地址,而且会帮你运行内存地址里面的代码。这也是函数名与普通变量名的区别。

print(函数名)就能看出来了
image
.
image

.
.
.
.

函数名的用法---1.可以将函数名赋值变量名

	def index():
	print('from index')
 	res = index
 	res()             # res它也有调用函数体代码的能力

.
.
.
.
.

函数名的用法---2.可以将函数名当做另一个函数的实参,传给另一个函数的行参,参与到另一个函数体代码的运行中去 重要!!!!

def index():
	print('from index')
def func(a):
	print(a)
	a()
func(index)

image
image
.
.
.
.
.

函数名的用法---3.可以当做另一个函数的返回值 重要!!!!

def index():
	print('from index')
def func():
	print('from func')
	return 123
res = func()             # 变量名接收函数体运行完后的返回值
print(res)

image
.
.
.
.

3.1 重要!!!!!!!!!!!!!!!!!!!!
def index():
	print('from index')
def func():
	print('from func')
	return index
res = func()             # 变量名接收函数体运行完后的返回值
print(res)               # 打印的就是index的内存地址
res()                    # 就是index(),运行的就是index函数体的代码

image
.
image
.
image
.
.
.
.
.
.
.

def index():
    print('from index')
def func():
    print('from func')
    return func
res = index()
print(res)
res()

.
.
.
.
.
.
.

3.2 返回值的重要作用!!!!!!
利用返回值的形式打破,名称空间的默认顺序,可以实现从全局找局部空间的变量名!!!!
res在全局但是拿到了局部空间func的内存地址,拿到了以后,res()就可以在全局空间,运行局部空间func的函数体代码了!!!

image
.
image
.
image
.
res拿到了局部空间func的内存地址,res()运行局部空间func的函数体代码了!!!
image
.
.
.
.
.
.

函数名的用法---4.可以当做容器类型(列表元组字典集合等)的数据值!!!

架构搭建就需要用到

像这样就可以通过调用列表里面的函数内存地址加括号,运行函数体的代码了
image

def register():
    print('注册功能')
def login():
    print('登录功能')
def withdraw():
    print('提现功能')
def transfer():
    print('转账功能')
def shopping():
    print('购物功能')

func_dict = {
    '1': register,
    '2': login,
    '3': withdraw,
    '4': transfer,
    '5': shopping
}
# 定义功能编号与功能的对应关系弄一个字典出来,编号作为键,函数名作为值,这样后面通过编号就能拿到函数名,也就拿到了函数体代码的内存地址,这时候用个变量接收一下,变量后面加个括号。就可以运行函数体代码了。
while True:
	print("""
        1.注册功能
        2.登录功能
        3.提现功能
        4.转账功能
        5.购物功能
        """)
	choice = input('请输入执行功能的编号:').strip()
	# if choice == '1':
	#     register()
	# elif choice == '2':
	#     login()
	# elif choice == '3':
	#     withdraw()
	# elif choice == '4':
	#     transfer()
	# elif choice == '5':
	#     shopping()
	# else:
	#     print('去你妹的 终于写完了')
	# 这样就可以不用执行上面的代码,就用下面的代码完成同样的功能,

	if choice in func_dict:
		func_name = func_dict.get(choice)
		func_name()       # 这个地方可以与上一行代码合并,直接这样func_dict.get(choice)() 就可以运行对应函数体的代码了。
	else:
		print('功能编号不存在')

# 尤其是当功能非常多,利用字典的形式,代码写的就非常方便了。

以后再做功能调用的代码永远就这几行了!!!!
image

.
.
.
.
.
.
.
.

闭包函数 重要!!!

给函数体代码传参的方式---闭包函数 : 再拿个函数包一下,再把参数传给它,这样就可以实现,传一次参,就可以一直调用了!!!还有个更加有用的地方是可以用在装饰器上!!!

闭包函数的套路是:在原来函数的基础上套了一层函数,先运行套在外面的函数体代码,代码的最后利用return返回里面的函数名称,再用变量名res接收里面的函数名称,最后就可以通过res()来运行被包在里面的函数体代码了!!!!!!

定义在函数内部的函数!!!
并且该内部的函数的函数体代码中,用到了外部函数名称空间中的名字!!!
同时符合这两点,才能叫做闭包函数。

闭包函数实际应用>>>:是另外一种给函数体代码传参的方式!!!

1.定义在函数内容
2.用到外部函数名称空间中的名字



def index():
    name = 'jason'
    def inner():       # inner就不是闭包函数,因为没有用到外部函数名称空间中的名字
        print('jason')    # 把'jason'改为name  inner就是闭包函数了

.
.
.

###给函数体代码传参的方式1:  直接在行参里面给
代码里面缺什么变量名,形参里面就补什么变量名!!!
def register(name,age):
    print(f"""
    姓名:{name}
    年龄:{age}
    """)
register('jason', 18)    # 调用函数体代码,并传参,结果就是
姓名:{jason}             # 但这个方法的缺点是,每次调用函数体代码,都要传参。
年龄:{18}

.
.
.


###给函数体代码传参的方式2: 再拿个函数包一下,再把参数传给它,这样就可以实现,传一次参,就可以一直调用了!!!还有个更加有用的地方是可以用在装饰器上!!!
闭包函数!!!
def outer():
    name = 'jason'
    age = 18
    def register():    # 此处register就是一个闭包函数,函数体代码中,用到了外部函数名称空间中的名字
        print(f"""
        姓名:{name}
        年龄:{age}
        """)
    return register
res = outer()
res()
res()             # res()就可以反复调用闭包函数register的函数体代码了


闭包函数的套路是:在原来函数的基础上套了一层函数,先运行套在外面的函数体代码,代码的最后利用return返回里面的函数名称,再用变量名res接收里面的函数名称,最后就可以通过res()来运行被包在里面的函数体代码了!!!!!!

image
.
image

1.闭包函数的作用:不受外界参数的改变而改变!!所以在外部的全局空间中的变量名对应的值怎么改,闭包函数register的代码的运行都不受影响。

image

.
.
.
.

2.闭包函数的真正作用!!!:当你有一些参数需要反复的传,反复的用,可以用闭包函数来简化传参的过程,传一次,以后就不用传了

def outer(name,age):
    def register():    # 此处register就是一个闭包函数,函数体代码中,用到了外部函数名称空间中的名字
        print(f"""
        姓名:{name}
        年龄:{age}
        """)
    return register
res = outer('jason', 18)
res()
res()             # res()就可以反复调用闭包函数register的函数体代码了
res = outer('kevin', 28)   # 改一下outer括号里面的参数
res()                      # 再运行register函数体代码时,形参对应的值就改掉了
res()

.
.
.
.
.

装饰器简介 重要!!!

1.概念:
在不改变被装饰对象原代码和调用方式!!!的情况下给被装饰对象添加新的功能!!!
2.本质:
并不是一门新的技术 而是由函数参数、名称空间、函数名多种用法、闭包函数组合到一起的结果
3.口诀:
对修改封闭 对扩展开放

4.储备知识  怎么样统计某一段代码运行的时间?
import time           # 功能模块
print(time.time())    # 时间戳(距离1970-01-01 00:00:00所经历的秒数)

代码实现1:
import time
count = 0
start_time = time.time()     # 循环之前先获取时间戳
while count < 100:
    print('哈哈哈')
    count += 1
end_time = time.time()       # 循环结束之后再获取时间戳
print('循环消耗的时间:', end_time - start_time)

补充知识:
import time 
time.sleep(3)     # 让代码原地等待3秒钟
print('睡醒了 干饭')

.
.
.
.
.

装饰器推导流程 重要!!!!!!!!!!

需求:   统计index函数的执行时间,但是再不改变原函数体代码与调用功能的情况下完成!!!   下面是函数体代码:
import time                      # 时间功能模块
def index():
    time.sleep(3)
    print('from index')
index()
代码如何实现?

第一种:!!!!!!!!

import time                      # 时间功能模块
def index():
    time.sleep(3)
    print('from index')
start_time = time.time()
index()                        # 1.直接在调用index函数的前后添加代码
end_time = time.time()
print('函数index的执行时间为>>>:', end_time-start_time)
完成,没有改变函数体代码及函数的调用方式,符合要求。但是这种代码有个问题,当想在多个地方调用index函数体代码并且统计函数体的运行时间时,就必须复制粘贴这4行代码:
start_time = time.time()
index()
end_time = time.time()
print('函数index的执行时间为>>>:', end_time-start_time)
这种操作就比较烦,index调用的地方较多 代码不可能反复拷贝。

第二种:!!!!!!!

相同的代码需要在不同的位置反复执行,思考可以采用函数的形式!!!把4行代码封装成一个函数!!!
代码实现:
def get_time():
    start_time = time.time()
    index()
    end_time = time.time()
    print('函数index的执行时间为>>>:', end_time - start_time)
get_time()
这样可以达到统计的效果,但是这时候调用方法变了,以前时调用index(),现在是调用get_time(),不符合我们前面的要求。所以直接封装代码达到我们的要求。
而且主要是函数体代码写死了,只能统计index的执行时间!!!
考虑变成有参的形式

第三种:!!!!!!!!

如何才能做到统计更多的函数运行时间??? 考虑直接传参来实现统计不同的函数运行时间
代码实现:
def get_time(xxx):
    start_time = time.time()
    xxx()
    end_time = time.time()
    print('函数的执行时间为>>>:', end_time - start_time)
get_time(index)
get_time(home)

这样就把无参函数变成了有参函数了!!!
这时候想统计不同函数体的运行时间,只需要直接把函数名传给get_time函数体代码就行了,代码被写活了。
虽然实现了一定的兼容性 但是并不符合装饰器的特征!!!还是改变了函数体的调用方式了!!
这是函数体代码传参的第一种的方式,代码里面缺什么,行参里面就补什么。!!!

第四种:!!!!!!!!!!

考虑闭包函数的形式进行函数体传参!!!!
怎么样把原来的函数变成闭包函数?必须是函数体代码是有参函数的形式下:
先写一行def outer(xxx):
然后把原来的函数体代码变成自定义函数outer的子代码
并把原来函数代码的名称get_time通过return返回出来,注意return这行代码是和def get_time():这行代码平行的。return get_time这行的目的就是,后续在闭包函数外部通过接收闭包函数的返回值拿到里面的函数名!!!!      重要!!!!要理解!!!!!
res = outer(输入你想要统计的函数名称)
这个时候res接收的就是get_time函数名,所以res = get_time  !!!!

代码实现:
def outer(xxx):    # 加的第一行
    def get_time():          # 把这个函数体代码搞成闭包函数的形式
        start_time = time.time()
        xxx()
        end_time = time.time()
        print('函数的执行时间为>>>:', end_time - start_time)
    return get_time    # 加的第二行
res = outer(index)     # 加的第三行
res()                  # 这时候就实现了闭包形式的函数体代码运行时间统计的效果
res1 = outer(home)     # 想统计其他函数体,再重新执行一下该代码,重新传参即可。
res1()

这一步虽然闭包实现了,但是函数的调用方式还是改变了!!!!

image
image

你调用的res(),而不是index() 调用方式还是不对,!!!

第五种:狸猫换太子!!!!!

思考如何变形?         
def outer(xxx):
    def get_time():
        start_time = time.time()
        xxx()
        end_time = time.time()
        print('函数的执行时间为>>>:', end_time - start_time)
    return get_time
res = outer(index)         # 赋值符号的左边是一个变量名 可以随意命名
res1 = outer(index)
res2 = outer(index)
jason = outer(index)       # 所以用这些变量名都行,不管变量名怎么变,下一行变量名()运行的还是get_time函数体代码
index = outer(index)       # 这一步是最骚的,index变量名实际还是绑定的是get_time函数体代码的内存地址
index()                   # 所以这时后index()实际上执行的还是get_time(),相当于你看着是执行index函数体代码,实际执行的是get_time函数体的代码!!!!!!!!!
home = outer(home)
home()                   # 这个也一样,看着是执行index函数体代码,实际执行的是get_time函数体的代码!!!!!!!!!

不同的函数,使用装饰器时,需要改的地方就这个index = outer(index),两边都写一样的函数名,左边指向的不是真正的该函数名对应的代码的内存地址,右边的函数名指向的是真正的该函数名对应的代码的内存地址。

这个时候装饰器已经符合要求了,调用方式也变成了index(),只是此时的index已经不是原来真正的index了。!!!!

image
image
这时候装饰器已经完成了,但是上述装饰器只能装饰无参函数 兼容性太差!!!!
.
.
.
.
.
.

思考过程:

考虑被装饰函数是有参的情况下,代码怎么继续优化:
def func(a):       # 定义了一个func函数,但是这个函数括号里面加了一个形参a
    time.sleep(0.1)
    print('from func', a)

def func1(a,b):
    time.sleep(0.2)
    print('from func1', a, b)

def func2():
    time.sleep(0.3)
    print('from func2')

def outer(xxx):
    def get_time():
        start_time = time.time()
        xxx()
        end_time = time.time()
        print('函数的执行时间为>>>:', end_time - start_time)
    return get_time
func1 = outer(func1)
func1(1, 2)
func = outer(func)
func(1)
func2 = outer(func2)
func2()

这时候运行func(123)实际是运行的get_time(123),但是get_time()原来不需要传参的,你给它传了,所以代码报错了!!!
image
给get_time函数定义阶段括号里面加一个行参比如a,变成get_time(a),这时候调用get_time(123)函数时,语法就没有问题了。但是代码运行还是报错了,为什么??
func函数变量名与xxx函数变量名都是指向的真实的函数体代码内存地址,那么当被装饰的真实的func函数有行参时,比如func(a),那么调用运行func()函数也就是XXX()时,括号里面必须要传一个实参,不然就不符合自定义函数语法了,一个自定义函数有位置形参,调用的时候又不传位置实参,所以语法又报错了!!!!!

image

.
.
.
.


首先考虑第一种情况:被装饰的函数是无参的情况
def func():,第五种就满足

第二种情况:被装饰的函数参数只有一个
def func1(a):此时XXX等于func1,所以xxx括号里面也一定要放一个a,不然代码一定会报错的

第三种情况:被装饰的函数参数只有一个
def func2(a,b,c):此时XXX等于func2,
所以xxx括号里面也一定要放一个a,b,c,不然代码一定会报错的


还有就是:用户实际上是不知道,装饰器已经将被装饰的函数的函数名所指向的内存地址给改掉了,
所以当被装饰函数是有参的情况下,用户还是会在调用index()运行时,
括号里面直接放对应的实参,假设被装饰函数
def index(a,b,c,d):   那么用户调用运行index函数时,
一定会传对应的参数比如index(1,2,3,4),此时index实际上时get_time,
所以自定义函数get_time括号()里面一定也要放上4个变量名去接收一下,
比如e,f,g,h    。



那么也就是说用户调用运行func()阶段,此时运行的是get_time()函数体代码,
所以在用户调用func()阶段,用户输入几个实参,
get_time函数在定义阶段就要有几个形参对应,那么问题来了,
用户还没调用阶段根本不知道,会传几个实参,
那我们在get_time函数在定义阶段的时候如何写满足形参才能满足了?
可变长形参无论你调用阶段输多少实参,我定义阶段只要一个可变长形参就一下全部接收了,
不需要用关键字形参和实参一一对应了!!!!!!
这样就实现了在用户调用func()阶段,无论输入几个实参,
都可以被get_time(*args,**kwargs)种*号或**号接收,
并赋值给对应args或KWargs组成元组或字典,所以在语法上没有问题。



再考虑XXX()括号里面怎么填?xxx对应的是被装饰函数的函数名,
在你还不知道被装饰的函数到底有几个形参,XXX()括号里面怎么填实参与对应的形参对应??


装饰器的功能是不影响原来函数的运行,所以用户虽然是给假的func()括号里面传值了,
但是我们需要的是,该传的值必须还要传到XXX的括号里面去,
才能实现给真正的func()函数传值的目的。


所以如何才能让给假的func函数(get_time函数)传的值能原封不动传到XXX()的括号里面???

只有一种方法,XXX()括号里面也这样(*args,**kwargs),
在实参中*args就会把args对应的元组与字典打散开来,这样以后get_time()接收到什么数据值,
就会原封不动传给XXX 。

.
.
.
.
.

第六种:


被装饰的函数不知道有没有参数以及有几个参数 如何兼容!!
代码继续优化:
def func(a):
    time.sleep(0.1)
    print('from func', a)
def func1(a,b):
    time.sleep(0.2)
    print('from func1', a, b)
def outer(xxx):
    def get_time(*args, **kwargs):  # get_time(1,2,3)  args=(1,2,3)
        start_time = time.time()
        xxx(*args, **kwargs)  # xxx(*(1,2,3))    xxx(1,2,3)
        end_time = time.time()
        print('函数的执行时间为>>>:', end_time - start_time)
    return get_time
func = outer(func)
func(123)
func1 = outer(func1)
func1(1, 2)

.
.
.

第七种:


如果被装饰的函数有返回值??怎么办???
代码继续优化:

def func(a):
    time.sleep(0.1)
    print('from func', a)
    return 'func'
def func1(a,b):
    time.sleep(0.2)
    print('from func1', a, b)
    return 'func1'
def outer(xxx):
    def get_time(*args, **kwargs):
        start_time = time.time()
        res = xxx(*args, **kwargs)
        end_time = time.time()
        print('函数的执行时间为>>>:', end_time - start_time)
        return res
    return get_time
func = outer(func)
res = func(123)
print(res)
func1 = outer(func1)

res = func1(123, 123)
print(res)

image
image

装饰器最终完成!!!

.
.
.
.
.
.

装饰器模板


# 务必掌握
def outer(func):
    def inner(*args, **kwargs):
        # 执行被装饰对象之前可以做的额外操作
        res = func(*args, **kwargs)
        # 执行被装饰对象之后可以做的额外操作
        return res
    return inner

.
.
.

装饰器语法糖


def outer(func_name):
    def inner(*args, **kwargs):
        print('执行被装饰对象之前可以做的额外操作')
        res = func_name(*args, **kwargs)
        print('执行被装饰对象之后可以做的额外操作')
        return res
    return inner
"""
语法糖会自动将下面紧挨着的函数名当做第一个参数自动传给@后面的函数调用运行
并用一个和函数名一样的变量名,去接收 @后面的函数运行的返回值!!!
"""
@outer  # func = outer(func)
def func():
    print('from func')
    return 'func'

@outer  # index = outer(index)
def index():
    print('from index')
    return 'index'

func()
index()

.
.
.
.
.

作业


1.整理今日内容及博客
2.尽量搞懂装饰器推导流程
3.编写一个用户认证装饰器
  函数:register login transfer withdraw 
  基本要求
   	 执行每个函数的时候必须先校验身份 eg: jason 123
  拔高练习(有点难度)
   	 执行被装饰的函数 只要有一次认证成功 那么后续的校验都通过
  提示:全局变量 记录当前用户是否认证
posted @ 2022-10-11 14:53  tengyifan  阅读(46)  评论(0编辑  收藏  举报