Python装饰器解析(1)——基础用法

1.装饰器功能

装饰器实质上是一个Python函数,主要功能是为已经原函数和对象添加额外的功能。
经常用于日志操作、用户登录、用户权限、数据读写操作前打开事务等之类需求。
能装饰函数和类,实现大量代码的重用,使代码更加简洁。、

2.装饰器使用

Python一切皆对象,函数也是一个function对象,所以能在函数中作为参数传递,例如

def info(func):
    print("info")
    return func


def login():
    print("login")


f = info(login)
f()

这个就是装饰器的简单原理,login函数作为参数传递到info函数上,然后再info的函数体调用login函数的逻辑代码,运行结果依次打印

info
login

1)函数装饰器,不带参数

def info(func):
    print("info")
    return func


@info #1
def login():
    print("login")


login()#2

该程序执行的效果跟上面的例子是一样的,@info是装饰器的语法糖写法。#1行相当于执行了login= info(login),#2行相当于执行了login()
如果info函数不返回func,#2行login()会报错TypeError: 'NoneType' object is not callable,也就是说加入装饰器后logininfo函数执行后的返回结果,也就是None

2)函数装饰器,带参数

def info(params):
    def wrap(func):
        print(params)
        return func

    return wrap


@info("hello") #1
def login():
    print("login")


login()#2

输出的结果是:

hello
login

从上面的程序可以看出@info("hello")相当于执行了

wrap = info("hello") 
login = wrap(login)

然后login得到wrap函数返回的结果,然后执行原来login函数的逻辑代码

login()

3)匹配带参数和不带参数的装饰器

def info(*args):
    if len(args) == 1 and callable(args[0]):  # 1 # hasattr(args[0], "__call__"):
        print("不带参数", end=" ")
        return args[0]  # 2
    else:
        print("带参数, 参数为%d" % args, end=" ")
        def wrap(func):
            return func  # 3

        return wrap


@info
def login():
    print("login")

login()

@info(100)
def logout():
    print("logout")


logout()

输出的结果是:

不带参数 login
带参数, 参数为100 logout

#1行len(args) == 1 and callable(args[0])检测args参数类型,我们知道@info相当于执行login = info(login),函数login作为参数传入info函数里面,那么args的长度为1,callable则判断参数是调用的(函数或者实现__call__的对象)。
因此可以看出login函数的装饰器进入infoif逻辑,而logout则进入else逻辑。

4)函数装饰器,装饰类

def hello(self):
    print("self = ", self)
    print("hello!!!")


def info(cls):
    if not hasattr(cls, "hello"):#2
        setattr(cls, "hello", hello)#3
    return cls


@info #1
class Login:
    def __init__(self):
        print("Login init")


login = Login()
login.hello()

输出结果是

Login init
self =  <__main__.Login object at 0x000001E57EAE9640>
hello!!!

之前的例子是装饰在函数上,而这个例子则装饰在类上,从输出的结果看出来,@infoLogin类额外添加hello成员函数。
#2判断Login是否已经定义了hello的函数,如果没有则通过#3行的setattr(object, name, value)内置函数动态添加成员函数。

在Python源码中也常遇到这种用法,例如functools库里面的total_ordering函数,看下面的例子

import functools

@functools.total_ordering  #1
class Login:
    def __init__(self, num):
        self.num = num

    def __gt__(self, other):
        return self.num - other.num > 0


a = Login(3)
b = Login(4)

print(a > b)
print(a >= b)#2

输出结果是

False
True

如果注释#1行的代码,#2行会报错

Traceback (most recent call last):
  File "D:/PycharmProjects/NewTest/decoratorTest.py", line 123, in <module>
    print(a <= b)#2
TypeError: '<=' not supported between instances of 'Login' and 'Login'

因此@functools.total_ordering会为Login类自动增加__le__的函数。

下面分析一下total_ordering的关键代码

_convert = {  #1
    '__lt__': [('__gt__', _gt_from_lt),
               ('__le__', _le_from_lt),
               ('__ge__', _ge_from_lt)],
    '__le__': [('__ge__', _ge_from_le),
               ('__lt__', _lt_from_le),
               ('__gt__', _gt_from_le)],
    '__gt__': [('__lt__', _lt_from_gt),
               ('__ge__', _ge_from_gt),
               ('__le__', _le_from_gt)],
    '__ge__': [('__le__', _le_from_ge),
               ('__gt__', _gt_from_ge),
               ('__lt__', _lt_from_ge)]
}

def total_ordering(cls):
    """Class decorator that fills in missing ordering methods"""
    # Find user-defined comparisons (not those inherited from object).
    #判断装饰的对象cls是否存在_convert字典中的函数,而且是否是object继承的,返回一个列表
    roots = {op for op in _convert if getattr(cls, op, None) is not getattr(object, op, None)}
    if not roots:
        raise ValueError('must define at least one ordering operation: < > <= >=')
    root = max(roots)       # prefer __lt__ to __le__ to __gt__ to __ge__
    #等到一个字典的value值后,遍历value的元组,判断cls是否存在其他函数,以函数的名字作为判断
    for opname, opfunc in _convert[root]:
        if opname not in roots:
            opfunc.__name__ = opname
            setattr(cls, opname, opfunc)
    #返回当前的类
    return cls

从源码中结合注释可以看出能够为对象自动生成__lt__,__le__,__gt__,__ge__等函数,从而时代码更加简洁。

5)对象装饰器,不带参数

前面讲的例子有一个共同点都是函数作为装饰器,下面使用对象作为装饰器。

class Info:
    def __init__(self, func):
        print("Info init")
        self.func = func

    def __call__(self, *args, **kwargs):
        self.func(*args, **kwargs)


@Info #1
def login(*args, **kwargs):
    print(args)


# login = Info(login)
print(type(login))#2
login("hello")

输出结果是

Login init
self =  <__main__.Login object at 0x000001ADF86F9640>
hello!!!

从输出结果看出,#2行的打印login是一个类,因此#1行中@Info相当于执行了login = Info(login),调用login("hello")会执行__call__函数逻辑。前面提到过callable内置函数,需要实现__call__才能调用(函数自带__call__)。

5)对象装饰器,带参数

class Info:
    def __init__(self, params):
        print("Info init")
        self.params = params

    def __call__(self, func):
        print("Info __call__")

        def wrap(*args, **kwargs):
            print("装饰器的参数为%s" % self.params)
            func(self.params)

        return wrap


@Info("hello")  # 1
def login(*args, **kwargs):
    print(args)

# info = Info("hello")
# login = info(login)
# print(type(login))
login() #2

输出结果是

Info init
Info __call__
装饰器的参数为hello
('hello',)

从输出结果看出#1行@Info("hello")相当于执行了

info = Info("hello")
login = info(login)

Info类中传入login作为参数并且执行,#2行login()相当于调用了wrap函数。

3.总结

从上面各个例子看出@info是装饰器特别的写法,其实质上也是一个函数或者是类,执行的都是de = info(),然后再以装饰的函数作为参数再次调用de(func)。看看下面的例子:

@a
@b
@c
def d():
	pass

相当于执行d = a(b(c(d)))(假设都返回当前函数),一步一步进行调用。

从装饰器的使用到语法上看出,装饰器能大大重用代码,而且使代码更加简洁,但是相对影响了可读性,需要深入学习才能理解透彻。

Python装饰器的基本用法暂到此结束。

下一篇介绍Retrying库的使用以及源码的分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值