由浅入深,走进Python装饰器-----第一篇:基础,原型

摘要: 本文从学习装饰器需要用到的基础知识入手, 用简单代码附以实例验证,在彻底理解基础知识的情况下帮助逐步理解, 装饰器的原型

要点: 从闭包及函数名可当参数传递作为基础理解思路, 由浅入深引入Python装饰器原型, 进而引出装饰器



**装饰器背景:**   在不改变原函数代码,  且保持原函数调用方法不变的情况下,  给原函数增加新的功能 (或者给类增加新属性和方法)


**核心思想: **      用一个函数(或者类) 去装修 一个旧函数 (或者类)   , 造出一个新函数(或者新类)

Python万物皆对象, 可以随意赋值使用

def func():
    print("第三: ","我是一个函数的输出结果")
a = 1
print("第一: 我是一个变量 ",a)
a = func                                                        #将函数名赋值给变量
print("第二: 我是一个函数名 ",a)
a()
>>>第一: 我是一个变量 1
>>>第二: 我是一个函数名 <function func at 0x000000000220AAE8>
>>>第三: 我是一个函数的输出结果

变量作用域

# 根据 LEGB原则, 内函数使用外函数的局部变量
def outer():
    a = 0
    print("我是外部函数的 a:",a)
    def inner():
        print("我是内部函数打印的 a:",a)

> > > 我是外部函数的 a: 0
> > > 我是内部函数打印的 a: 0

# 引入 nonlocal, 内函数可以修改外函数
def outer():
    a = 0
    print("我是外部函数的 a:",a)
    def inner():
        nonlocal a
        a += 1
        print("我是内部函数加工过的 a:",a)

>>>我是外部函数的 a: 0
>>>我是内部函数加工过的 a: 1

闭包( 延长局部变量生存时间 )

# 内函数  夹带  外函数的局部变量
def outer():
    a = 0
    print("我是外部函数打印的 a:",a)
    def inner():
        print("我是内部函数打印的 a:",a)
    return inner

res = outer()                                        # res接收的是一个函数名
print("我是返回出来的内函数名: "res)
res()

>>>我是外部函数打印的 a: 0
>>>我是返回出来的内函数名: <function func at 0x000000000220AAE8>
>>>我是内部函数打印的 a: 0
>>>
#  内函数  夹带  修改后的外函数的局部变量
def outer():
    a = 0
    print("我是外部函数打印的 a:",a)
    def inner():
        nonlocal a
        a += 1
        print("我是内部函数修改过的 a:",a)
    return inner

res = outer()                                       # res接收的是一个函数名
print("我是返回出来的内函数名: "res)
res()

>>>我是外部函数打印的 a: 0
>>>我是返回出来的内函数名: <function func at 0x000000000220CA97>
>>>我是内部函数修改过的 a: 1

函数名可以当做参数传递

#新函数对旧函数修饰
def old():
    print('我是 旧函数')     
def new(f):
    print('我是 新函数, 我可以输出 旧函数')
    f() 

new( old )                        #把旧函数名当做参数, 传递给新函数, 在新函数内产生变化

>>>我是 新函数, 我可以输出 旧函数
>>>我是 旧函数

装饰器原型

# ### 利用闭包,将函数名当参数传递
def outer(f):
    def inner():
        print("我是外部传进来的old :",f)
        f()
        print("我是修饰工,我在旧函数后显示")
    return inner

def old():
    print("我是旧函数")

print("我是最初的old: ",old)                         
print("我是返回出来的内函数inner: ",outer(old))   #outer把函数名old当参数传回函数内部进行加工, 把函数名inner返回
old = outer(old)                            #实际上, 旧函数名old已被重新赋值为inner
print("我不是原来的old了 ",old)
old()                                       #经过改造, 虽然调用结果不变, 但是运行结果已变化

>>>我是最初的old:               <function old at 0x00000000021CAA60>
>>>我是返回出来内函数inner:     <functionouter<locals>.inner at 0x00000000021CAAE8>
>>>我不是原来的old了            <function outer.<locals>.inner at 0x00000000021CAAE8>
>>>我是外部传进来的old :        <function old at 0x00000000021CAA60>
>>>我是旧函数
>>>我是修饰工,我在旧函数后显示

装饰器

# ### 用 @outer 修饰
def outer(f):
    def inner():
        print("我是外部传进来的old :",f)
        f()
        print("我是修饰工,我在旧函数后显示")
    return inner

@outer                       #实际上,将  old = outer(old)  这句改成 @outer, 放在old函数上方
def old():
    print("我是旧函数")

old()                                                                 
>>>我是外部传进来的old : <function old at 0x00000000021CAA60>
>>>我是旧函数
>>>我是修饰工,我在旧函数后显示

装饰器的嵌套: 下层装饰器修饰后的函数, 再次被上层装饰器修饰

def kuozhan(f):
    def inner():
        print("1我是扩展函数,接收外部传进来的新old :",f)
        f()
        print("4我是扩展函数,我在新old函数后显示")
    return inner

def outer(f):
    def inner():
        print("2我是outer函数,接收外部传进来的old :",f)
        f()
        print("3我是outer函数,我在old函数后显示")
    return inner

@kuozhan               #old = kuozhan(outer(old))
@outer                 #old = outer(old)
def old():
    print("我是old函数")

old()

>>>1 我是扩展函数,接收外部传进来的新old : <function outer.<locals>.inner at 0x000000000250AB70>
>>>2 我是外部传进来的old :               <function old at 0x000000000250AAE8>
>>>  我是old函数
>>>3 我是修饰工,我在旧函数后显示
>>>4 我是扩展函数,我在新old函数后显示

相关推荐:

如何在Python中使用asyncio.shield防止关键任务被取消?

asyncio.shield必须用于关键清理操作(如日志落盘、事务提交、连接关闭),防止被取消中断导致数据不一致;应只包裹协程本身(shield(coro)),而非Task,且内部子await需单独shield。 asyncio.shield什么时候必须用? 当一个asyncio.Task正在执行关...

为什么Python单例模式在多线程环境下会失效?

__new__中判空非原子操作会导致多线程重复创建实例;DCL通过无锁初判+加锁后复判解决,需配合__init__幂等防护。 __new__中的if判断不是原子操作 线程A执行到ifcls._instanceisNone时为True,刚准备调用super().__new__(cls),此时被调度挂起...

如何修复Python aiohttp请求时出现的ClientPayloadError?

ClientPayloadError表明服务端提前关闭连接,非客户端代码错误;常见于服务端超时、拒绝大响应或代理截断,需通过抓包、检查响应头、服务端日志及添加读取超时和异常处理来定位解决。 ClientPayloadError通常意味着服务端提前关闭了连接 这个错误不是客户端代码写错了,而是aioh...

为什么在大型Python项目中推荐使用Monorepo环境管理?

Monorepo是应对跨子包频繁修改、版本对齐成本高、CI耗时爆炸等痛点的止损方案;需统一依赖管理、确保本地与CI环境一致,并严守子包独立测试边界。 Monorepo不是“更先进”,而是对特定规模和协作模式的项目更可控——当Python项目中出现跨子包频繁修改、版本对齐成本高、CI耗时爆炸或本地开发...

为什么Python爬虫在解析复杂表格时推荐使用Pandas库?

pandas.read_html能自动解析HTML表格并修复合并单元格、多级表头等结构,但需合理配置match、header、skiprows等参数,并处理JS渲染、数据类型、空值等问题,否则易导致列错位、类型错误等隐患。 因为pandas.read_html能直接把HTML表格转成DataFram...

为什么在Python项目中推荐使用Poetry管理包依赖?

poetryinstall与pipinstall-rrequirements.txt的本质差异在于:前者是“解析后装”,基于poetry.lock中已验证的完整依赖图确保版本兼容并自动激活专属虚拟环境;后者是“盲装”,仅按文本顺序安装、不解析依赖冲突、不隔离环境,易导致静默覆盖和全局污染。 因为pi...