一.闭包

1.1 闭包的定义与作用

1.1.1 闭包定义

闭包:在嵌套函数中,内部函数引用了外部函数的变量 / 参数,且外部函数返回内部函数对象,这个「内部函数 + 它引用的外部变量」的组合,就是闭包。

1.1.2 闭包的作用

让外部函数的局部变量,在外部函数执行完毕后,依然能被内部函数访问、保存,实现数据的持久化和封装。

1.2 闭包的必要条件

1.有嵌套:在函数嵌套 (函数里面再定义函数) 的前提下;

2.有引用:内部函数使用了外部函数的变量 (还包括外部函数的参数);

3.有返回:外部函数返回了内部函数名。

1.3 闭包的格式

def 外部函数名(形参列表):
    外部函数的(局部)变量

    def 内部函数名(形参列表):
        使用外部函数的变量

    return 内部函数名

1.4 nonlocal关键字

作用:在嵌套函数(内部函数 / 闭包)中,声明并修改「外层函数的局部变量」,而不是创建新的局部变量。

示例代码:

def outer():
    count = 0  

    def inner():
        nonlocal count
        count += 1
        return count

    return inner

counter = outer()

print(counter())  
print(counter()) 
print(counter()) 

输出结果:

该代码图解:

global和nonlocal区别:

global:操作全局变量(整个模块顶层的变量)

nonlocal:操作外层嵌套函数的局部变量(闭包专用)

二.装饰器

2.1 作用

在不修改原函数代码、不改变原函数调用方式的前提下,给函数动态添加额外功能。

一个返回函数的外层函数 + 内层包装函数 = 高级闭包(装饰器的本质)

2.2 构成条件

1.有嵌套:在函数嵌套 (函数里面再定义函数) 的前提下;

2.有引用:内部函数使用了外部函数的变量 (还包括外部函数的参数);

3.有返回:外部函数返回了内部函数名;

4.有额外功能:给需要装饰的原有函数增加额外功能。

2.3 格式

def 外部函数名(形参列表):
    外部函数的(局部)变量

    def 内部函数名(形参列表):
        额外拓展的功能
        使用外部函数的变量

    return 内部函数名

2.4 用法

2.4.1 传统写法

装饰后的函数名 =  装饰器名(原函数名)

装饰后的函数名()

2.4.2 语法糖

原函数上写@装饰器名,之后直接调用原函数

2.5 示例

2.5.1 原函数有(无)参有(无)返回值
import functools

def log_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[日志] 开始执行 {func.__name__}")
        result = func(*args, **kwargs)
        print(f"[日志] 结束执行 {func.__name__}")
        return result
    return wrapper

1.无参无返回

@log_decorator
def hello():
    print("Hello World")

hello()

结果:

2.无参有返回

@log_decorator
def get_name():
    return "张三"

res = get_name()
print("返回值:", res)

结果:

3. 有参无返回

@log_decorator
def print_info(name, age):
    print(f"姓名:{name},年龄:{age}")

print_info("张三", 18)

结果:

4.有参有返回

@log_decorator
def add(a, b):
    return a + b

res = add(10, 20)
print("计算结果:", res)

结果:

2.5.2 可变参数
def my_decorator(fn_name):
    def fn_inner(*args, **kwargs):
        print("...")
        return fn_name(*args, **kwargs)
    return fn_inner
@my_decorator
def get_sum(*args,**kwargs):
    return sum(args) + sum(kwargs.values())

sum = get_sum(1,2,3,x=1,y=2,z=3)
print(sum)

结果:

2.5.3 多个装饰器装饰一个函数
def decorator_A(func):
    def wrapper():
        print("装饰器A:执行前置操作")
        func()
    return wrapper

def decorator_B(func):
    def wrapper():
        print("装饰器B:执行前置操作")
        func()
    return wrapper


@decorator_B
@decorator_A
def my_func():
    print("我是被装饰的核心函数")


my_func()

结果:

多个装饰器装饰一个函数按照由内到外的顺序修饰,效果上是由上向下执行

2.5.4 一个装饰器装饰多个函数
from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"=== 开始执行 {func.__name__} ===")
        result = func(*args, **kwargs)
        print(f"=== 结束执行 {func.__name__} ===\n")
        return result
    return wrapper


@my_decorator
def add(a, b):
    return a + b

@my_decorator
def multiply(a, b):
    return a * b

print("结果:", add(1, 2))
print("结果:", multiply(3, 4))

结果:

三. 拷贝

3.1 定义

浅拷贝:通过 copy 模块的 copy.copy() 实现
深拷贝:通过 copy 模块的 copy.deepcopy() 实现

3.2  作用

装饰器会覆盖原函数的元信息(函数名、文档字符串、参数注解等),我们需要通过元信息拷贝还原原函数的特征。

进阶场景会用到浅拷贝 / 深拷贝处理函数参数、对象的引用问题。

3.3 区别

3.3.1 针对可变类型

浅拷贝:仅拷贝第 1 层的可变对象,嵌套的深层可变对象仍然共享原对象的引用(修改深层数据,原对象和拷贝对象会互相影响)
深拷贝:递归拷贝所有层级的可变对象,生成完全独立的新对象(修改任何层级的数据,原对象和拷贝对象都互不干扰)

3.3.2  针对不可变类型

深浅拷贝的效果和普通赋值(=)完全一致

对象创建后,能否在不改变内存地址(id)的前提下,直接修改自身存储的值
能 → 可变类型    list(列表)、dict(字典)、set(集合)
不能 → 不可变类型    int(整数)、float(浮点数)、str(字符串)、tuple(元组)、bool(布尔)、frozenset(冻结集合)

3.4 示例

针对可变类型的浅拷贝和深拷贝

import copy

a = [1, 2, [3, 4]]

shallow = copy.copy(a)
deep = copy.deepcopy(a)

a[2][0] = 0

print("原对象:", a)
print("浅拷贝:", shallow)
print("深拷贝:", deep)

结果:

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐