python基础之闭包函数与装饰器

python基础之闭包函数与装饰器

一、global与nonlocal

money = 66  # 全局空间


def index():
    money = 123
    
    
index()
print(money)
image-20221011145926078
1.global 局部名称空间直接修改全局名称空间中的数据(不可变类型:整型、字符串和元组)

# 案例1
money = 66  # 全局空间

def index():
    global money  # global作用 局部名称空间直接修改全局名称空间中(不可变类型)的数据,此时函数体代码内的数据值可以绑定全局名称空间中的变量名
    money = 123
    
index()
print(money)  # 此时 money = 123

# 案例2 
money = 66  # 全局空间
l1 = [11,22,33]
def index():
    l1.append(44)  # 此时修改的是全局的l1,列表为可变类型的数据
    
index()
print(l1)

只有赋值语法才涉及 名称存放 和 在 名称空间查找名字 的问题

2.nonlocal 内层局部名称空间 修改 外层局部名称空间中的数据
# 案例1
 # 不使用关键字nonlocal
def index():
    name = 'jason'
    def inner():
        name = 'tom'
    inner()
    print(name)
index() # jason 

# 使用关键字nonlocal
def index():
    name = 'jason'
    def inner():
        nonlocal name  # 内层局部名称空间 修改 外层局部名称空间中的数据
        name = 'tom'
    inner()
    print(name)
index()  # 函数index 局部名称空间中 name = tom
image-20221011152158989

二、函数名的多种用法

函数可以被当作“数据”来处理,此时可以将函数名看作是变量名

  函数名其实绑定的也是一块内存地址,只不过改地址里面存放的不是数据值,而是一段代码,函数名加括号可以去调用这段代码
  
# 案例1
def index():
    print('from index')
print(index)  # <function index at 函数名所绑定的内存地址>

1.可以当变量名

1.可以当作变量名赋值
# 案例2
def index():
    print('from index')
res = index  # 将res绑定向index函数
res()  # res也具备调用index的能力

2.可以当函数的参数

2.可以当作函数的参数传值进去
# 案例3
def index():
    print('from index')
def func(a):
    print(a)
    a()
func(index)
image-20221011153642125

print(函数名) 为打印 该函数的内存地址

print(函数名()) 为打印该函数的返回值

3.可以当函数的返回值

3.可以当作函数的返回值
 1)返回值是数据值时,直接返回
# 案例4 
def index():
    print('from index')
    
def func():
    print('from func')
    return 123

res = func()
print(res)  # 123

 2)返回值是函数名时,赋值于其他变量名可以利用该变量名调用函数
  ( 1 )两个函数套娃
# 案例5
def index():
    print('from index')
    
def func():
    print('from func')
    return index

res = func()
print(res)
res()  # from index
10.11 插图4.png
 ( 2 )函数嵌套
# 案例6
def index():
    print('from index')
    def func():
        print('from func')
    return func
  
res = index()
print(res)
res()  # 让res也绑定向func绑定的值,res也能调用func中的
image-20221011161834717

函数嵌套时,名称空间的绑定关系
image-20221011161834717

4.可以当作容器类型(可以存放多个数据的数据类型:列表、元组、字典、集合)的数据
# 案例6
def index():
    print('from index')

l1 = [11, 22, 11.11, 'jason', index]
l1[-1]()  # from index
# 通过索引 列表中 的 元素 函数名 去调用函数

# 案例7 通过将函数名保存在字典中,索引函数名 使用函数功能
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 in func_dict:
        func_dict.get(choice)()
    else:
        print('功能编号不存在')
image-20221011171930571

三、闭包函数

闭包函数:定义在函数内部的函数,并且用到了外部函数空间的名字,闭函数是内部的函数,包函数是外部的函数
  也就是函数被当作数据处理的时候,始终以自带的作用域为准,若内嵌函数包含外对外部函数作用域中变量的引用,那么该'内嵌函数'就是闭包函数
'''	满足的要求:
  1.该函数定义在函数内部
  2.用到了外部函数名称空间的名字(变量名)'''
# 案例1 
def index():
    print('jason')
    def inner():
        print(name)  # 是变量名而不是数据值

闭包函数的实际应用

1.传参方式:将值包给函数

1.闭包函数是另一种给函数体代码传参的方式,就是将该值包给函数 
# 闭包函数的传参方式,对实现装饰器的作用有很大用处

# 函数体传参的方式一:代码里面缺什么变量名就在形参中补充什么变量名
def register(name, age):
    print(f'''
    name:{name}
    age:{age}
    ''')
  
# 函数体传参的方式二:闭包函数
def outer():
    name = 'jason'
    age = 12
    def register(name, age):
        print(f'''
        name:{name}
        age:{age}
        ''')
    return register
res = outer()
# 闭包函数特点1:不会受外界变量名的影响

1.闭包函数中名称空间与代码体的绑定关系

image-20221012151330546 image-20221012153315027

2.闭包函数中代码运行的顺序

image-20221011182426248

当第5步的x 没有被使用时,会变灰,即pycharm提示当前程序内并没有使用到这个对象,只要在程序内没有其他代码使用它的时候就会变灰来表示没有被引用可以删除不占用资源

2.绑定一次数据值可以重复使用

# 闭包函数的特点2:绑定一次数据值可以重复使用
def outer(name, age):
    def register(name, age):
        print(f'''
        name:{name}
        age:{age}
        ''')
    return register
res = outer('jason', 18)
res()
res()
res = outer('kevin', 38)
res()
res()
res()
image-20221011184817270 image-20221011185247791

1.在1次传参后,可以重复使用
2.可以通过改变传进去的参数,对结果进行更改

四、装饰器

1.装饰器简介

1.概念
    在不改变被装饰对象原代码和调用方式的情况下,给被装饰对象添加新的功能
  """
  软件包含功能的源代码以及调用方式,都应该避免修改,避免出错。
  而对于上线后的软件,新需求则意味着扩展的可能性,应该为程序提供扩展的可能性
  """
2.本质
    并不是一门新的技术,而是由函数参数、名称空间、函数名多种用法、闭包函数组合到一起的结果
3.装饰器口诀
    对修改封闭,对扩展开放
   """
  软件的设计应该遵循开放封闭原则,对扩展开放,对修改封闭。对扩展开放意味着有新的需求或者变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改。
  """
4.储备知识
    时间相关操作 
 1)时间戳 time.time
import time
print(time.time())  # 时间戳(距离1970-01-01 00:00所经历的秒数)
 2)时间阻塞 time.sleep()
import time
time.sleep()

2.装饰器推导流程

# 案例
import time
def index():
    time.sleep(3)
    print('from index')
def home():
    time.sleep(1)
    print('from home')

如果想为案例中的代码添加新的功能,该如何实现?

1.直接在调用index函数前后添加新功能的代码

import time
def index():
    time.sleep(3)
    print('from index')
def home():
    time.sleep(1)
    print('from home')
    
start_time = time.time()
index()
end_time = time.time()
print('函数index的执行时间为:', end_time-start_time)

如果想要多次调用index函数如何实现新功能,该如何实现?

2. 定义新功能的函数实现多次调用index函数新功能

相同的代码不同位置反复执行==>>> 函数
定义新功能为函数,即可通过调用函数多次使用新功能

def get_time():
    start_time = time.time()
    index()
    end_time = time.time()
    print('函数index的执行时间为:', end_time - start_time)


get_time()
image-20221011191952414

但是这样函数体代码不够灵活,只能对index函数使用,如何对其他函数使用?

3.使用参数可以传参变换统计的函数

利用参数,将统计的函数名传给参数,变换传参即变化统计的函数
#参数是函数内,外界和函数内部交流的媒介,可以通过参数从外界传数据进去函数体内部
def get_time(v):
    start_time = time.time()
    v()
    end_time = time.time()
    print('函数index的执行时间为:', end_time - start_time)


get_time(home)
get_time(index)
---------------res--------------------
from home
函数index的执行时间为: 1.003127098083496
from index
函数index的执行时间为: 3.0050511360168457

如何通过原调用函数的方式,去使用新功能?

4.使用闭包函数来传参

通过闭包函数来将传参的方法变简便
上述直接分装形参传值不写,不写参数,可以用闭包函数
def outer(v):
    def get_time():
        start_time = time.time()
        v()
        end_time = time.time()
        print('函数index的执行时间为:', end_time - start_time)
    return get_time

res = outer(index)
res()
res = outer(home)
res()
---------------res--------------------
from index
函数index的执行时间为: 3.005207061767578
from home
函数index的执行时间为: 1.004741907119751

如何使用原函数名来调用具有新功能的函数?

5.使用赋值语句使调用原函数名时具有新功能

将原函数名 绑定向 扩展了新功能的函数体代码
def outer(v):
    def get_time():
        start_time = time.time()
        v()
        end_time = time.time()
        print('函数index的执行时间为:', end_time - start_time)
    return get_time

index = outer(index)
index()
home = outer(home)
home()
---------------res--------------------
from index
函数index的执行时间为: 3.005207061767578
from home
函数index的执行时间为: 1.004741907119751
image-20221011200429696

如果想调用有参函数怎么办?

6.为闭包函数引入参数,兼容有参函数

考虑想要装饰的函数有参数、以及返回值  
# 装饰器在不调用的时候,装饰器不会执行
# 推导可得被装饰的函数需要几个参数get_time 就应该加几个参数
import time

def index(a):
    time.sleep(3)
    print('from index',a)

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


def outer(res):
    def get_time(*args, **kwargs):
        start_time = time.time()
        res(*args, **kwargs)
        end_time = time.time()
        print('函数index的执行时间为:', end_time - start_time)

    return get_time


index = outer(index)
index(1)  # 原函数的调用方式和函数名都没改变
func1 = outer(func1)
func1(1, 2)  # 原函数的调用方式和函数名都没改变
---------------res--------------------
from index 1
函数index的执行时间为: 3.005033016204834
from func1 1
函数index的执行时间为: 0.20508694648742676

如果函数有返回值,则需要为装饰器添加返回值

7.为闭包函数引入返回值,兼容返回值

添加返回值import time

import time

def index(a):
    time.sleep(3)
    print('from index',a)
    return 'index'  # 有返回值

def func1(a, b):
    time.sleep(0.2)
    print('from func1', a)
    return 'func1'  # 有返回值

def outer(func):
    def get_time(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)  # 变量名res 接收 调用函数func()的返回值
        end_time = time.time()
        print('函数index的执行时间为:', end_time - start_time)
        # 当想要装饰的函数有返回值时,也需要为装饰器添加返回值,用一个变量名res来接收返回值
        return res
    return get_time


index = outer(index)  # index = get_time index可以调用装饰器内部函数get_time
res1 = index(1)  # 用变量名res1 接收index函数的返回值
print(res1)  # 打印index函数的返回值

func1 = outer(func1)
res2 = func1(1,2)
print(res2)
---------------res--------------------
from index 1
函数index的执行时间为: 3.005223035812378
index
from func1 1
函数index的执行时间为: 0.20263981819152832
func1
image-20221012161339902

3.装饰器模版

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

4.装饰器语法糖

用符号@装饰器的函数名,调用该装饰器,且把它正下方的函数名当作是参传入,然后将返回的结果重新赋值给原函数名
def outer(func):
    def inner(*args, **kwargs):
        # 执行被装饰对象之前可以做的额外操作
        res = func(*args, **kwargs)
        # 执行被装饰对象之后可以做的额外操作
        return res
    return inner
"""
	语法糖的原理:
	
语法糖 @装饰器的函数名,调用该装饰器,且把它正下方的函数名当作是参数传入@后面函数名,并调用,然后将返回的结果重新赋值给原函数名
"""
@outer  # func = outer(func)
def func():
    print('from func')
    return 'func'
posted @ 2022-10-11 20:38  Duosg  阅读(71)  评论(0编辑  收藏  举报