函数装饰器/闭包函数/nonlocal/global使用

197 阅读12分钟
  • global和nonlocal用法
  • 函数名的多种用法
  • 闭包函数
  • 装饰器介绍
  • 装饰器推导流程(原理)
  • 装饰器模板
  • 装饰器语法糖
  • 多层语法糖
  • 有参装饰器
  • 装饰器模板
  • 装饰器修复技术
  • 递归函数
  • 练习题及答案

1.global与nonlocal用法

1.
global 主要是用在局部名称空间,它可以在局部名称空间直接修改全局名称空间的数据
2.
nonlocal 主要是用在局部名称空间,它可以在局部名称空间直接修改外部(上一层)局部名称空间的数据
例1:
name = 55
def func():
    global name  # 让局部中的name也可以修改全局的name
    name = 11
func()
print(name)
# 在名称空间的概念中我们知道全局名称空间的作用域和局部名称空间的作用域是不一样的
# 所以我们此时输出的name 就是全局名称空间里面name与55的绑定关系
# 我们如果想要输出func函数里面的内容的话我们就需要在函数func函数体代码中添加global关键字并且在后面添加需要修改的变量名name
# 这样我们就可以输出函数里面的变量了2def func():
	name = 11
    def subd():
    	nonlocal name
    	name = 22
    subd()
    print(name)
func()
# 在局部空间我们想要修改外部(上一层)的局部名称空间,我们可以在函数体代码下面添加nonlocal关键字并且在后面加上需要修改的变量名就可以了

2.函数名的多种用法

函数名绑定的也是一块内存地址,只不过内存地址存放的不是数据值而是函数下面的函数体代码
我们打印函数名(不加括号)他会出现:
# <function func at 0x0000027720E261F0> 相当于函数名的名片
我们加上括号就可以调用此函数名对应的函数体代码
因此函数名的多种用法使用如下:
1.函数名可以作为参数
def func1():pass
def func(func1):
	print(func1)  # <function func1 at 0x000002539BAB61F0>
func(func1)

2.函数名可以赋值给变量名
def func():
	print('i am real man')
data = func() # 不加括号就返回函数名的名片 加括号就会直接执行func函数 并且将func()的返回值赋值给data
print(data)
# <function func at 0x000001738D9F61F0> 
# None

3.函数名可以当作函数的返回值
例1def func1():
    print('真棒')
def func():
	print('你好')
    return(func1)
data = func() # func函数体代码的返回值赋值给data 所以我们赋值的时候只需要把函数名赋值给data即可,不要加括号!!!!!!!!!!
data()  # 此时的data为None  None() 会报错!!!!!
print(data) # None2def func():
    print('hello world')
    return func
def func1():
    print('hello girl')

data = func()  # hello world
print(data)  # <function func at 0x000001A5EBD161F0>
data()  # hello world

4.可以作为容器数据类型里面的数据值(可以存放多个数据的数据类型)
# 我们需要写一些登录注册功能
# 在此之前我们一直都是使用if elif 来做用户选择的判断,但是这种方式面多功能较多的项目时确实不实用
# 所以我们可以应用函数名的多种用法,把函数名当作数据值放在容器类数据类型里
def register():
    print('注册功能')
def login():
    print('注册功能')
def shopping():
    print('购物功能')
def check_account():
    print('查看账户功能')
def quit():
    print('退出功能')
choice_data = {
    '1':register,
    '2':login,
    '3':shopping,
    '4':check_account,
    '5':quit
}
while True:
    print(
          '1.注册功能\n'
          '2.登录功能\n'
          '3.购物功能\n'
          '4.查看账户功能\n'
          '5.退出功能\n'
    )
    user_choice = input('请输入您想要执行的功能编号>>>>>:')
    if user_choice in choice_data:
        choice_data.get(user_choice)()
    else:
        print('请选择已存在功能的编号')
# 这样的写法优化了我们之前if elif 重复判断的麻烦,
# 并且 我们在修改功能的时候只需要把字典内部对应的功能以及其函数删除即可!

3.闭包函数

1.闭包的定义
'''
定义在函数内部的函数,并且用到了外部函数名称空间中的名字
	1.定义在函数内部的函数
	2.用到外部函数名称空间中的名字
'''
def func():  
    name = 'xiaoming'  
    def func1():
        print(name)
    func1()
func()
2.闭包函数的实际应用
是另外一种给函数体代码传参的方法
# 之前总结的传参:
def func(name,password):
    print(f'姓名:{name}\n密码:{password}')
func('小明',123)

函数需要什么参数我们传什么参数即可
但是这种方法不方便修改,我们每次使用函数都需要在里面填值

# 当我们使用闭包函数:
def outer(name,password):
    def inner():
        print(f'姓名:{name}\n密码:{password}')
    return inner
data = outer('小明',1234)  # outer的返回值赋值给data 相当于data = inner
data() # 调用inner函数
这样我们只要调用data() 那么就会把
姓名:小明
密码:1234
结果呈现出来,需要的时候只要重新写 
data = outer(name,password)
然后继续调用data()即可。

4.装饰器介绍

1.装饰器的概念
# 在不改变被装饰对象原代码和调用方式的情况下给被装饰对象添加新的功能
2.装饰器的本质
# 装饰并不是新计数,而是由函数参数、名称空间、函数名的多种用法、闭包函数结合到一起的结果
3.口诀
	# 对修改封闭,对扩展开放
4.装饰器前戏(储备知识)
调用模块
import time  # 调用时间模块
# time 是唤起模块 + . + xx()  可以调用模块中的函数
# print(time.time())  时间戳(距离1970-01-01 00:00:00所经历的秒数)
time.sleep(2)  #  延迟()内秒数 再执行代码
print('就是牛蛙')
有一个需求是计时一下这个代码从运行到结束 过去了多少时间:
import time
# print(time.time())
count = 0
start_time = time.time()
while count < 100:
    print('牛蛙')
    count += 1
end_time = time.time()
print(f'从运行开始到结束一共过去了', end_time - start_time)

我们就可以这样实现此功能

5.装饰器原理推导过程

我们在前面装饰器前戏部分已经提到了需求,我们现在需要通过函数来实现它

# 在不改变被装饰对象原代码和调用方式的情况下给被装饰对象添加新的功能
import time
def func():
    time.sleep(2)
    print('你好,牛蛙')
def func1():
	time.sleep(1)
    print('好的哥')
我们想要计算它开始运行到结束所需的时间
我们可以:
import time  # 调用时间模块
def func():
    time.sleep(2)  # 延迟2秒
    print('你好,牛蛙') 
def func1():
    time.sleep(1)
    print('好的哥')
start_time = time.time() 开始时间
func()  # 调用func函数
end_time = time.time()
print(f'从运行开始到结束一共过去了', end_time - start_time)  # 获取结果

# 但是我们不能做到上述#号内要求
# 所以我们应该在此基础上再进行优化,把上述代码封装成函数执行

import time
def func():
    time.sleep(2)
    print('你好,牛蛙')
def get_time():
    start_time = time.time()
    func()
    end_time = time.time()
    print(f'从运行开始到结束一共过去了', end_time - start_time)
get_time()

# 我们把它封装成了函数,做到了再不同的地方反复执行此功能,但是我们函数体代码写死了,只能统计一个函数的运行时间,我们要考虑怎么优化,让它可以去更多的统计别的函数的运行时间

import time
def func():
    time.sleep(2)
    print('你好,牛蛙')
def func1():
    time.sleep(1)
    print('好的哥')
def get_time(function):
    strat_time = time.time()
    function()
    end_time = time.time()
    print(f'从运行开始到结束一共过去了', end_time - start_time)
get_time(func)
get_time(func1)

结果:
你好,牛蛙
从运行开始到结束一共过去了 2.01809024810791
好的哥
从运行开始到结束一共过去了 1.0098073482513428

# 我们可以通过传参数,将对应的变量名传输到我们定义好的函数中,这样就可以满足统计多个不同函数的运行时间
# 但是这离我们定义的装饰器还是很不一样,我们如果要满足装饰器的条件,我们就得考虑使用闭包函数了
# 闭包函数的定义是在函数内部的函数,并且用到了外部函数名称空间中的名字。

我们接下来来满足一下它的要求:
import time
def func():
    time.sleep(2)
    print('你好,牛蛙')
def func1():
    time.sleep(1)
    print('好的哥')
def outer(function):
	def inner():
        strat_time = time.time()
    	function()
    	end_time = time.time()
         print(f'从运行开始到结束一共过去了', end_time - start_time)
	return inner
data = outer(func)
data()  #  你好,牛蛙  从运行开始到结束一共过去了 2.012845754623413
dtaa = outer(func1)
data()  # 好的哥  从运行开始到结束一共过去了 1.020143985748291

# 这是我们通过闭包函数的形式得出来的代码,但是我们的调用方式还是不对,只能调用赋值的代码

接下来,我们一起来看看 data 我们能不能改成func 函数名呢?
import time
def func():
    time.sleep(2)
    print('你好,牛蛙')
def func1():
    time.sleep(1)
    print('好的哥')
def outer(function):
	def inner():
        strat_time = time.time()
    	function()
    	end_time = time.time()
         print(f'从运行开始到结束一共过去了', end_time - start_time)
	return inner
func = outer(func)
func()  
func1 = outer(func1)
func1()  
# 我们运行后的结果还是可行的,inner 函数 确实被返回并且被func 变量名接收了
# 我们调用func()就能调用inner函数的功能
# 但是前提我们需要在func1()前面写好我们的赋值,不赋值还是不能给函数绑定装饰器
# 并且我们没有写有参数的情况,都是func没有参数的情况,我们看一下它们有参数的情况

上述装饰器只能装饰无参函数,兼容性不好,我们在看看如何装饰有参函数
import time
def func(a):
    time.sleep(2)
    print('你好,牛蛙',a)
def func1(a,b):
    time.sleep(1)
    print('好的哥',a,b)
def outer(function):
    def inner(a,b):
        strat_time = time.time()
    	function(a,b)
    	end_time = time.time()
         print(f'从运行开始到结束一共过去了', end_time - start_time)
	return inner
func1 = outer(func1)
func1(1,2)

# 如果它有参数我们要跟据参数的不同来调整 inner 里面 传递参数的内容
# 我们不确定到底有需要被装饰的函数到底有几个需要传递的参数,所以,我们要想办法解决这个问题

那么我们可以看一下以下操作,
import time
def func(a):
    time.sleep(2)
    print('你好,牛蛙',a)
def func1(a,b)
	time.sleep(1)
    print('好的哥',a,b)
def func2():
    time.sleep(1.5)
    print('明白了')
def outer(function):
    def inner(*args,**kwargs):
        start_time = time.time()
        function(*args,**kwargs)
        end_time = time.time()
        print('函数的执行时间为>:',end_time - start_time)
	return inner
func = outer(func)
func(1)
func1 = outer(func1)
func1(2,3)
func2 = outer(func2)
func2()
# 这样的话我们对于被装饰函数需要传参数的问题就解决了,不管是传参数还是不传参数都可以解决
# 但是我们还没有考虑到被装饰函数有返回值的情况,所以我们需要在看一下有返回值的情况

我们来看一下被装饰函数有返回值的情况

import time
def func(a):
    time.sleep(2)
    print('你好,牛蛙',a)
    return 666
def func1(a,b):
    time.sleep(1)
    print('好的哥',a,b)
    return 777
def func2():
    time.sleep(1.5)
    print('明白了')
    return 888
def outer(function):
    def inner(*args,**kwargs):
        start_time = time.time()
        b = function(*args,**kwargs)
        end_time = time.time()
        print('函数的执行时间为>:',end_time - start_time)
        return b
    return inner
func = outer(func)
b = func(1)
print(b)
func1 = outer(func1)
b = func1(1,2)
print(b)
func2 = outer(func2)
b = func2()
print(b)

有返回值的时候我们需要在 inner函数内给返回值赋值一个变量名,并且 return 这个返回值
这样我们打印这个变量名就可以获取到它的返回值
到此我们的装饰器也就算圆满完成任务。

6.装饰器模板

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

7.装饰器语法糖

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()

8.多层语法糖

"""
多层语法糖 加载顺序由下往上
每次执行之后如果上面还有语法糖 则直接将返回值函数名传给上面的语法糖
如果上面没有语法糖了 则变形 index = outter1(wrapper2)
"""
在我们正常使用语法糖的时候一般是@装饰器
这样的一个操作
我们先来看一下一层语法糖
我们先写一个装饰器
def outer(function):
    def inner(*args,**kwargs):
        res = function(*args,**kwargs)
        return res
    return inner
@outer  # 语法糖的作用就是帮我们优化了一下执行装饰器的代码 省掉了 变量名 赋值符号 函数名(函数名)
def func():
    print('我是Func')
func()
例子1:多层语法糖
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
@outter2
@outter3
def index():
    print('from index')
index()

9.有参装饰器

# 校验用户是否登录装饰器
def outer(mode):
    def login_auth(func_name):
        def inner(*args,**kwargs)
        	username = input('username>>>>:').strip()
             password = input('password>>>>:').strip()
             if mode == '1':
                print('使用模式1')
             elif mode == '2':
            	print('使用模式2')
             elif mode == '3':
        		print('使用模式3')
             res = func_name(*args,**kwargs)
             return res
        return inner
     return login_auth
'''
当装饰器中需要额外的参数时,我们需要在给装饰器增加一层传参
在函数名加括号是执行优先级最高的
	我们应该先看函数名加括号的执行
	在看语法糖的操作
'''
# @outer('1')
def index():
    print('from index')
index()

# @outer('2')
def func():
    print('from func')
func()

10.装饰器模板

无参装饰器模板(常用)
def outer(function):
    def inner(*args,**kwargs):
    	res = function(*args,**kwargs)
        return res
    return inner
有参装饰器模板(不太用)
def outer_plus(x):
	def outer(function):
        def inner(*args,**kwargs)
        	res = function(*args,**kwargs)
            return res
        return inner
    return outer
@outer_plus('33'
def func():
    print('你好')
func()

11.装饰器修复技术

在介绍修复技术之前介绍一个函数方法:
help()
它可以帮助我们查看一些函数的具体注释内容
我们知道被装饰器装饰的函数其实已经不是它自己本身了,如果我们想要help()它,它显示的是装饰器部分的内容
所以我们如果想要让help()函数显示它自己本身的内容的话我们可以在
装饰器开头加上 
from functools import wraps
def outer(func_name):
    @wraps(func_name)
    def inner(*args,**kwargs):
        res = func_name(*args,**kwargs)
        return res
    return inner
# 通过这种方法我们就可让help()括号内填写被装饰器修饰的函数名,结果显示也是函数名内的信息,这样就可以对装饰器做到以假乱真,更方便用户去查看这个函数

12.递归函数

函数的递归调用
	函数直接或者间接调用了函数自身
例子:
直接调用
def index():
    print('from index')
    index()
index()
我们执行这个index() 但是会一直重复在这个函数里面,这样我们的函数内部名称空间就在不断的增加存储量
我们的Python解释器它会在一定次数后停止这个递归,是为了保护计算机放置爆掉内存
间接调用:
def index():
    print('123')
    func()
def func()
	print('456')
    index()
func()
这两个函数互相调用自己本身,也跟直接调用一样,如果python解释器没有干预的话我们的内存会被它们产生的信息填满
# 最大递归深度为:官方标注:1000
也就是我们最多是能执行1000次,我们的python解释器会自动帮我们停止操作

递归函数:
1.直接或间接调用自己(函数)
2.每次调用都必须比上一次简单,并且有一个需要明确的结束条件
	递推:一层层往下
    回溯:基于明确的结果一层层往上
 	 """
    get_age(5) = get_age(4) + 2
    get_age(4) = get_age(3) + 2
    get_age(3) = get_age(2) + 2
    get_age(2) = get_age(1) + 2
    get_age(1) = 18
    """
    def get_age(n):
        if n == 1:
            return 18
        return get_age(n-1) + 2
    res = get_age(5)
    print(res)
    

13.练习题及答案

1.利用递归函数依次打印列表中每一个数据值
	l1 = [1,[2,[3,[4,[5,[6,[7,[8,]]]]]]]]
	
	# l1 = [1,[2,[3,[4,[5,[6,[7,[8,]]]]]]]]
#
# def look(l1):  # 形参传递 l1列表
#     for i in l1:  # 遍历 l1 列表
#         if type(i) == list:  # 如果 i 的类型是列表 那么 再次调用 look函数 并且实参变为i
#             look(i)
#         else:
#             print(i)
#
# look(l1)  # 调用函数并传入实参 l1
2.利用有参装饰器编写多种用户登录校验策略

login_dict = {'uesr':None}

def outer_plus(mode):
    def outer(func):
        def inner(*args,**kwargs):
            if login_dict.get('user'):
                if mode == '1':
                    print('模式1')
                    res = func(*args, **kwargs)
                    return res
                elif mode == '2':
                    print('模式2')
                    res = func(*args, **kwargs)
                    return res
                elif mode == '3':
                    print('模式3')
                    res = func(*args, **kwargs)
                    return res
                elif mode == '4':
                    print('模式4')
                    res = func(*args, **kwargs)
                    return res
            else:
                username = input('请输入用户名>>>>>:')
                password = input('请输入密码>>>>>:')
                if username == 'jason' and password == '123':
                    login_dict['user'] = True
                    if mode == '1':
                        print('模式1')
                        res = func(*args, **kwargs)
                        return res
                    elif mode == '2':
                        print('模式2')
                        res = func(*args, **kwargs)
                        return res
                    elif mode == '3':
                        print('模式3')
                        res = func(*args, **kwargs)
                        return res
                    elif mode == '4':
                        print('模式4')
                        res = func(*args, **kwargs)
                        return res
                else:
                    print('用户名或密码错误!')
        return inner
    return outer


@outer_plus('1')
def register():
    print('注册功能')
@outer_plus('2')
def login():
    print('登录功能')
@outer_plus('3')
def transfer():
    print('转账功能')
@outer_plus('4')
def withdraw():
    print('提现功能')


choice_dict = {'2': register,
               '1': login,
               '3': transfer,
               '4': withdraw
               }
while True:
    print('1.登录\n'
          '2.注册\n'
          '3.转账\n'
          '4.提现\n'
)

    choice = input('请输入您想要执行的功能>>>>:')
    if choice in choice_dict:
        choice_dict.get(choice)()
    elif choice.lower() == 'q':
        print('程序结束')
        break
    else:
        print('请重新输入功能编号')
尽量搞懂装饰器推导流程
编写一个用户认证装饰器
  函数:register login transfer withdraw
  基本要求
   	 执行每个函数的时候必须先校验身份 eg: jason 123
  拔高练习(有点难度)
   	 执行被装饰的函数 只要有一次认证成功 那么后续的校验都通过
  提示:全局变量 记录当前用户是否认证
  
  # login_dict = {'uesr':None}
#
# def outer(func):
#     def inner(*args,**kwargs):
#         if login_dict.get('user'):
#             res = func(*args, **kwargs)
#             return res
#         else:
#             username = input('请输入用户名>>>>>:')
#             password = input('请输入密码>>>>>:')
#             if username == 'jason' and password == '123':
#                 login_dict['user'] = True
#                 res = func(*args, **kwargs)
#                 return res
#
#             else:
#                 print('用户名或密码错误!')
#     return inner
#
#
#
# @outer
# def register():
#     print('注册功能')
# @outer
# def login():
#     print('登录功能')
# @outer
# def transfer():
#     print('转账功能')
# @outer
# def withdraw():
#     print('提现功能')
#
#
# choice_dict = {'2': register,
#                '1': login,
#                '3': transfer,
#                '4': withdraw
#                }
# while True:
#     print('1.登录\n'
#           '2.注册\n'
#           '3.转账\n'
#           '4.提现\n'
# )
#
#     choice = input('请输入您想要执行的功能>>>>:')
#     if choice in choice_dict:
#         choice_dict.get(choice)()
#     elif choice.lower() == 'q':
#         print('程序结束')
#         break
#     else:
#         print('请重新输入功能编号')