python之装饰器

预备知识:https://www.cnblogs.com/Zzbj/p/9495066.html

一、函数名的应用

复制代码
# 1,函数名就是函数的内存地址,而函数名()则是运行这个函数。
def func():
    return

print(func)  # 返回一个地址

# 2,函数名可以作为变量。
def func1():
    print(666)

f1 = func1
f2 = f1
f2()  # 就等于func1() 此时执行函数

# 3,函数名可以作为函数的参数。
def func1():
    print(666)

def func2(x):
    x()

func2(func1)  # 输出666 func1作为实参传进func2中,此时在func2中x()等于func1()

# 4,函数名可以当做函数的返回值。
def wraaper():
    def inner():
        print(666)
    return inner

ret = wraaper()  # 执行函数wraaper(),得到返回值inner赋值给ret,这个inner是函数名
ret()  # 输出666,ret()等于inner()
# (注意:这里要调用inner()只能是用ret(),因为inner()是wraaper()的嵌套函数,在全局命名空间中并没有声明,所以直接用inner()不能执行,)

def func2():
    print('in func2')

def func3(x):
    print('in func3')
    return x

f = func3(func2)  # 给func3传进一个参数func2,返回func2赋值给f
f()  # f()等于func2()

# 5,函数名可以作为容器类类型的元素。
def f1():
    print('f1')

def f2():
    print('f2')

def f3():
    print('f3')

l = [f1, f2, f3]
d = {'f1': f1, 'f2': f2, 'f3': f3}
# 调用
l[0]()  # 输出 f1
d['f2']()  # 输出 f2


def func1():
    print('in func1')

def func2():
    print('in func2')

def func3():
    print('in func3')

def func4():
    print('in func4')

l1 = [func1,func2,func3,func4]

#调用
for i in l1:
    i()

# 像上面的函数名这种,称为第一类对象。
# 第一类对象(first-class object)指:
# 1.可在运行期创建
# 2.可用作函数参数或返回值
# 3.可存入变量的实体。
# (如果不明白,那就记住一句话,就当普通变量用)
复制代码

 

二、闭包

复制代码
1、定义
内层函数对外层函数(非全局)的变量的引用,这个内层函数就成为闭包。
在Python中,我们可以使用__closure__来检测函数是否是闭包。
有cell元素的是闭包。


例如:
def func():
    name = '番薯'

    def func2():
        print(name)  # 引用外层函数的变量
    func2()
    print(func2.__closure__)  # (<cell at 0x000002591EA794F8: str object at 0x000002591EACB500>)

func()
print(func.__closure__)  # None


2、闭包的例子
2-1、这里并没有引用外层函数的变量,而是把外层函数的变量传给func2,所以不算闭包
def func1():
    name = '番薯'

    def func2(arg):
        print(arg)
    func2(name)
    print(func2.__closure__)
    
    
func1()


2-2def func1():
    name = '番薯'

    def func2():
        print(name)  # 引用外层函数的变量,形成闭包
    func2()
    print(func2.__closure__)


func1()


2-3def func1(name):
    def func2():
        print(name)  # 引用外层函数的变量,形成闭包
    func2()
    print(func2.__closure__)


func1('番薯')


3、闭包的应用
3-1、把内部函数(闭包)当成返回值返回就可以使用闭包了
def func1():
    name = '番薯'

    def func2():
        print(name)

    return func2  # 把内部函数当成是返回值返回


ret = func1()  # 把返回值赋值给变量ret
ret()  # 调用内部函数


3-2、多层嵌套的闭包
def func1():
    def func2():
        def func3():
            print('func3')
        return func3
    return func2


f2 = func1()  # func2
f3 = f2()  # func3
f3()


4、闭包的好处
可以在任何时间从外界访问到内部函数。
但是我们知道一个函数执行完毕后,这个函数中的变量以及局部命名空间中的内容都是会被销毁的。
在闭包中如果变量被销毁了,那内部函数就不能正常执行了。
所以一旦这个内部函数引用了外层函数中的变量形成了闭包,那么这个变量将不会随着外层函数的结束而销毁,它会在内存中保留。
也就是说,闭包可以保留变量的引用。
复制代码

 

三、装饰器初识

复制代码
1、开放封闭原则
软件设计的原则: 开闭原则, 又被成为开放封闭原则。
开放封闭原则是指对扩展代码的功能是开放的,
但是对修改源代码是封闭的。
这样的软件设计思路可以保证我们更好的开发和维护我们的代码。


比如:你开了一家老字号(下面的代码就相当于源码)
def old_shop():
    print('离百年老字号还差99年')


old_shop()


然后你的老店要新增空调(相当于新增功能):
def old_shop():
    print('新增空调')
    print('离百年老字号还差99年')


old_shop()

这样添加功能确实是可以的,但是违背了开放封闭原则,直接在源码上进行修改了。
那怎么办呢?新建一个函数不就好了
def old_shop_with_conditioner():
    print('新增空调')
    print('离百年老字号还差99年')


old_shop_with_conditioner()

但是,问题又来了,你的老字号很火,开了很多分店,每家分店都是调用了之前的old_shop函数开店,
那么你修改了之后,所有调用原来函数的分店都需要重新调用新的函数,这么做其实是很番薯的行为。

那么如何在不改变函数的结构和调用方式的基础上,动态的给函数添加功能呢?
可以利用闭包
def old_shop():
    print('离百年老字号还差99年')


def a(func):
    def b():
        print('新增空调')
        func()
    return b


ret = a(old_shop)  # 内层的b函数
ret()

然后问题又来了,现在虽然没有直接修改源码,但是函数名还是改变了,那又怎么办?
重命名不就行了吗:
def old_shop():
    print('离百年老字号还差99年')


def a(func):
    def b():
        print('新增空调')
        func()
    return b


old_shop = a(old_shop)  # 内层的b函数
old_shop()

这样不就遵循了开发封闭原则了吗,即没有修改源码,扩展代码又是开放的,也没有改变函数原来的调用方式


2、装饰器语法糖
刚才上面的代码只是装饰器的原理和雏形,Python中针对于上面的功能提供了一个快捷的写法,俗称装饰器语法糖。
使用装饰器语法糖的写法,实现同样功能的代码如下:
def a(func):  # a是我们定义的装饰器函数,func是被装饰的函数(old_shop)
    def b():
        print('新增空调')
        func()
    return b


@a    # 相当于把被装饰的函数old_shop当成参数传给a,然后把返回值b再重新赋值给被装饰的函数名old_shop
def old_shop():
    print('离百年老字号还差99年')


old_shop()  # 相当于调用了内层函数b
复制代码

 

四、装饰器进阶

复制代码
1、装饰带返回值的函数
def wrapper(func):
    def inner():
        print('新功能')  # 你要新增的功能
        result = func()  # 拿到被装饰函数的返回值
        return result  # 返回被装饰的函数的返回值
    return inner


@wrapper
def f():
    return 'Hello'


ret = f()
print(ret)


2、装饰带参数的函数
def wrapper(func):
    def inner(x, y):  # 实际执行函数的参数
        print('新功能')
        r = func(x, y)
        return r
    return inner


@wrapper
def my_sum(x, y):
    return x + y


ret = my_sum(1, 2)  # inner(1, 2)
print(ret)

但是一般来说,我们把参数设置成动态参数会更便于拓展
若是三个数相加,或者四个数相加,只需要修改my_sum的参数就可以了
def wrapper(func):
    def inner(*args, **kwargs):
        print('新功能')
        r = func(*args, **kwargs)
        return r
    return inner


@wrapper
def my_sum(x, y, z):
    return x + y + z


ret = my_sum(1, 2, 3)
print(ret)


3、带参数的装饰器(也叫装饰器工厂)
装饰器如果要带参数的话,可以嵌套更多层:
def outer(arg):
    def wrapper(func):
        def inner(*args, **kwargs):
            print('欢迎来到%s' % arg)
            func(*args, **kwargs)
        return inner
    return wrapper


@outer('英雄联盟')  # 会先执行outer,然后返回函数名wrapper,相当于@wrapper,在闭包内还能使用最外层的函数变量
def lol():
    print('这里是召唤师峡谷')


@outer('地下城与勇士')  # @wrapper
def dnf():
    print('这里是阿拉德大陆')


lol()
dnf()


4、装饰器修复技术

被装饰的函数最终都会失去本来的__doc__ __name__等信息,就是说,如果这个函数被装饰了,
那么它里面的文档信息(注释信息,通常注释信息很重要,注释写明了改函数的功能和参数的信息等)就会消失,
 Python给我们提供了一个修复被装饰函数的工具,用于找回这些信息。
from functools import wraps


def wrapper(func):
    @wraps(func)
    def inner(*args, **kwargs):
        print('这是新功能')
        func(*args, **kwargs)
    return inner


@wrapper
def f1(x, y):
    """
    这里写这个函数的主要功能
    :param x: 这个参数的类型
    :param y: 这个参数的类型
    :return: 返回值
    """
    print('我是帅哥')


print(f1.__doc__)  # 打印这个函数的文档信息(注释内容)
print(f1.__name__)  # 打印这个函数名



5、多个装饰器装饰同一函数
def wrapper1(func):
    print('w1')

    def inner1():
        print('inner1')
        return '<i>{}</i>'.format(func())
    return inner1


def wrapper2(func):
    print('w2')

    def inner2():
        print('inner2')
        return '<b>{}</b>'.format(func())
    return inner2


@wrapper1
@wrapper2
def f1():
    return "小明"


ret = f1()
print(ret)

结果:
w2
w1
inner1
inner2
<i><b>小明</b></i>

分析:
在装饰阶段会直接执行装饰函数,并拿到返回值,即
@wrapper2  --->  wrapper2(f1)  ---> print('w2') ---> return inner2 ---> 把变量f1重新指向inner2
@wrapper1 --->  wrapper1(f1)[此时的f1实际上是inner2]  ---> print('w1') ---> return inner1 ---> 把变量f1重新指向inner1
然后执行f1()相当于执行inner1()
print('inner1')
return '<i>{}</i>'.format(func())
此时的func是传进来的参数inner2,所以又去执行inner2
print('inner2')
return '<b>{}</b>'.format(func())
此时的func是传进来的参数f1[被装饰的f1],所以拿到返回值<b>小明</b>,拼接到inner1的返回值那里,最后
<i><b>小明</b></i>
复制代码

 

五、两层且带参数的装饰器

复制代码
"""
装饰器格式:@ + 函数名
装饰器原理:把被装饰的函数名传递到装饰器,然后把装饰器的返回值重新赋值给被装饰函数
"""
1、标准装饰器
def desc(desc):
    def decorate(cls):
        print("desc:%s, cls:%s" %(desc, cls))
        return cls

    return decorate
    

# 把normal传到desc,把返回值decorate重新赋值给normal
# 下面执行normal()实际上是执行了decorate()
@desc
def normal(cls):
    pass

normal("normal字符串")  # desc:<function normal at 0x01E1E300>, cls:normal字符串


2、两层且带参数的装饰器
def desc(desc):
    def decorate(cls):
        print("desc:%s, cls:%s" %(desc, cls))
        return cls

    return decorate    


# 先执行desc("测试函数"),得到decorate
# 下面等于是:@decorate, 把normal传到decorate,把返回值cls重新赋值给normal
@desc("测试函数")
def test():
    print("test")

# 结果:desc:测试函数, cls:<function test at 0x01E1E270>


@desc("测试类")
class TestAPI():
    pass

# 结果:desc:测试类, cls:<class '__main__.TestAPI'>
复制代码

 

六、数码暴龙进化装饰器

复制代码
1、类装饰器
我们除了可以使用函数装饰函数外,还可以用类装饰函数。
"""
先理清一下装饰器的思路:
1、
def desc(func):
    def inner(*args, **kwargs):
        pass
    return inner

@desc
def test():
    pass
这样的装饰器,其实就是:
1.执行装饰器函数desc,并把被装饰的函数test当中参数传进去,即desc(test)
2.将desc的返回值inner重新赋值给test,即test=inner
3.执行test()即执行 inner()


2、
def outter(x):
    def desc(func):
        def inner(*args, **kwargs):
            pass
        return inner
    return desc

@outter('嘿嘿')
def test():
    pass
这样的装饰器,其实就是:
0.先执行outter('嘿嘿'),获得函数desc,装饰器其实也是@desc
1.执行装饰器函数desc,并把被装饰的函数test当中参数传进去,即desc(test)
2.将desc的返回值inner重新赋值给test,即test=inner
3.执行test()即执行 inner()

3、那么类装饰器就可以有两种
1.使用类实例化对象时候 __init__方法
2.使用调用对象的 __call__ 方法

"""


# 1.使用类实例化对象时候 __init__方法
class Page(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print('before............')
        print("args: %s" % args)
        res = self.func(*args, **kwargs)
        print('after............')
        return res


@Page  # 相当Page() 也就是实例化对象,即 @__init__(index),实例化完成后会返回一个对象
def index(name):  # 此时的index就是Page的对象
    print("Hello %s" % name)


index('index')  # 对象() 会调用 __call__
# 结果
"""
before............
args: index
Hello index
after............

相当于
def __init__(func):
    def __call__(*args, **kwargs):
        res = func(*args, **kwargs)
        return res
    return __call__
    
@__init__
def index(name):
    print("Hello %s" % name)
"""


# 2.使用调用对象的 __call__ 方法
class Page(object):
    def __init__(self, desc):
        self.desc = desc

    def __call__(self, func):
        def inner(*args, **kwargs):
            print("inner")
            res = func(*args, **kwargs)
            return res

        return inner


# 先执行Page(实例化的参数")得到实例化对象
# @实例化对象,即 对象(), 即@__call__(index)
@Page("实例化的参数")
def index(name):  # 此时的index就是inner
    print("Hello %s" % name)


index('index')  # 执行inner
# 结果
"""
inner
Hello index
"""


2、装饰类
上面所有的例子都是装饰一个函数,返回一个可执行函数。Python中的装饰器除了能装饰函数外,还能装饰类。
可以使用装饰器,来批量修改被装饰类的某些方法


# 定义一个类装饰器
class D(object):
    def __call__(self, cls):
        class Inner(cls):
            # 重写被装饰类的f方法
            def f(self):
                print('Hello 番薯')

        return Inner


@D()
class C(object):  # 被装饰的类
    # 有一个实例方法
    def f(self):
        print("Hello world.")


if __name__ == '__main__':
    c = C()
    c.f()
复制代码

 

七、装饰器小结(重)

复制代码
1、装饰器的标准结构
from functools import wraps


def wrapper(func):  # func:被装饰的函数
    @wraps(func)  # 把func指向的函数的__doc__、__name__等属性复制到inner上面
    def inner(*args, **kwargs):  # *args和**kwargs是被装饰函数的参数
        print('新功能')
        r = func(*args, **kwargs)
        print('新功能也可以在这里')
        return r
    return inner


@wrapper  # 此时会执行wrapper,所以wrapper必须定义在这一行之前
def hello():
    print('Hello World!')


hello()


2、带参数的装饰器(也叫装饰器工厂)
from functools import wraps


def outer(k=None):
    def wrapper(func):
        @wraps(func)
        def inner(*args, **kwargs):
            if k == 'start':
                print('节目开始')
                r = func(*args, **kwargs)
                return r
            else:
                print('节目还未开始')
        return inner
    return wrapper


@outer('start')
def hello():
    """这里是hello函数"""
    print('Hello World!')


hello()
print(hello.__doc__)
print(hello.__name__)


3、多个装饰器同时装饰一个函数
"""
给Hello World!包两层标签,
<div><p>Hello World!</p></div>
"""
from functools import wraps


def wrapper1(func):  # 包p标签
    @wraps(func)
    def inner1(*args, **kwargs):
        r = func(*args, **kwargs)
        return '<p>{}</p>'.format(r)
    return inner1


def wrapper2(func):  # 包div标签
    @wraps(func)
    def inner2(*args, **kwargs):
        r = func(*args, **kwargs)
        return '<div>{}</div>'.format(r)
    return inner2


@wrapper2
@wrapper1
def hello():
    return "Hello World!"


print(hello())
复制代码

 

posted @   我用python写Bug  阅读(323)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示