作者:吉星高照, 网易游戏资深开发工程师,主要工作方向为网易游戏 CDN 自动化平台的设计和开发,脑洞比较奇特,喜欢在各种非主流的领域研究制作各种不走寻常路的东西。

Python的装饰器是面试的常客,因为其写法复杂多变,经常忘记什么地方应该写哪种参数,新手学习起来也经常一头雾水,不怕不怕,看了这一篇你对装饰器的各种用法就全明白了。废话不多说,直接进入主题!

不带参数的函数,不带参数的装饰器

我们先来写一个简单的装饰器,实现将函数运行前后的情况记录下来。

def dec1(func):
    print(func)
    def _wrap():
        print('before run')
        r = func()
        print('after run')
        return r
    return _wrap

@dec1
def f1():
    print('call f1')

上面只是定义了两个函数,运行后发现竟然有输出:

<function f1 at 0x7fa1585f8488>

仔细看看,原来是第一个 print 语句的输出。这说明装饰的函数还没有实际运行的时候,装饰器就运行过了,因为 @dec1 相当于单独的一个语句:

dec1(f1)

那我们来正式运行一下 f1 吧:

f1(1)

输出如下确实达到了预期的效果:

before run
call f1
after run

不带参数的函数,带空参数的装饰器

后面我们还想要给装饰器加上参数呢,先试试用这个方式调用会发生什么情况:

@dec1()

输出了错误:

Traceback (most recent call last)
<ipython-input-268-01cf93cf6907> in <module>
      8     return _wrap
      9
---> 10 @dec1()
     11 def f1():
     12     print('call f1')

TypeError: dec1() missing 1 required positional argument: 'func'

它说 dec1 需要接受 func 这个参数才行,那我们改改,作为 f2 函数吧:

def dec2():
    def _wrap(func):
        print(func)
        print('before run')
        return func
    return _wrap

@dec2()
def f2():
    print('call f2')

f2()

这下可以了:

<function f2 at 0x7fa1585af2f0>
before run
call f2

可是这个结构和原来有点不同了,而且, after run 要写在哪里呢?很愁人地又改了一版,把它叫做 f2x 吧,对比 dec1 ,又多了一层函数 dec2x_w ,开始有点晕:

def dec2x_w():
    def dec2x(func):
        print(func)
        def _wrap():
            print('before run')
            r = func()
            print('after run')
            return r
        return _wrap
    return dec2x

@dec2x_w()
def f2x():
    print('call f2x')

f2x()

运行一下看看,确实是想要的:

<function f2x at 0x7fa1585af950>
before run
call f2x
after run

后面我们就不加 before/after run 了。

带参数的函数,不带参数的装饰器

函数 f2x 想要接受参数呢?我们把它叫做 a 吧,比较简单,不就是 _wrap 的参数吗,加上就是了,而且又回到了只有两层函数就可以实现了:

def dec3(func):
    print(func)
    def _wrap(param):
        print(param)
        r = func(param)
        return r
    return _wrap

@dec3
def f3(a):
    print('call f3', a)

f3(1)

很争气地输出了结果:

<function f3 at 0x7fa158719620>
1
call f3 1

带参数的函数,带参数的装饰器

下面我们实现一个装饰器,传入一个整数,和函数传入的参数做个加法:

def dec4_w(d_param):
    print(d_param)
    def dec4(func):
        print(func)
        def _wrap(param):
            print(param)
            r = func(param + d_param)
            return r
        return _wrap
    return dec4

@dec4_w(2)
def f4(a):
    print('call f4', a)

f4(1)

输出 1+2=3 :

2
<function f4 at 0x7fa1585af598>
1
call f4 3

从调用的装饰器往里看,注意这三层函数的形参,第一层是装饰器的参数,第二层是函数,第三层是函数的参数,很有规律的排列,先记一下这个规律(要考的)。带两个参数的也是一样的,接着写就可以了:

def dec5_w(d_param_1, d_param_2):
    print(d_param_1, d_param_2)
    def dec5(func):
        print(func)
        def _wrap(param):
            print(param)
            r = func(param + d_param_1 + d_param_2)
            return r
        return _wrap
    return dec5

@dec5_w(2, 3)
def f5(a):
    print('call f5', a)

f5(1)

输出 1+2+3=6 :

2 3
<function f5 at 0x7fa1586237b8>
1
call f5 6

如果用不定数量的位置参数,就用 *args 作为形参吧:

def dec6_w(*args):
    d_param_1, d_param_2, = args
    print(d_param_1, d_param_2)
    def dec6(func):
        print(func)
        def _wrap(*args):
            param = args[0]
            print(param)
            r = func(param + d_param_1 + d_param_2)
            return r
        return _wrap
    return dec6

@dec6_w(2, 3)
def f6(a):
    print('call f6', a)

f6(1)
print(f6.__name__)

顺便输出了一下 f6 的函数名:

2 3
<function f6 at 0x7fa1586236a8>
1
call f6 6
_wrap

咦!怎么肥四!!! f6 怎么是里面那个 _wrap 的名字呢?不怕不怕, functools 提供了一个 wraps 装饰器专治各种不服(在装饰器里面放上另一个装饰器):

from functools import wraps

def dec7_w(*args):
    d_param_1, d_param_2, = args
    print(d_param_1, d_param_2)
    def dec7(func):
        print(func)
        @wraps(func)
        def _wrap(*args):
            param = args[0]
            print(param)
            r = func(param + d_param_1 + d_param_2)
            return r
        return _wrap
    return dec7

@dec7_w(2, 3)
def f7(a):
    print('call f7', a)

f7(1)
print(f7.__name__)

这下正常输出 f7 了:

2 3
<function f7 at 0x7fa1585f8510>
1
call f7 6
f7

装饰器类(带参数的函数,带参数的装饰器)

用函数做装饰器局限性太多了,用相同的调用方法,把函数 f7 改成类怎么样?emmm…改造工程有点大,直接看看成品吧:

from functools import wraps

class dec8_c:
    def __init__(self, *args):
        self.d_param_1, self.d_param_2, = args
        print(self.d_param_1, self.d_param_2)
    def  __call__(self, func):
        print(func)
        @wraps(func)
        def _wrap(param):
            print(param)
            r = func(param + self.d_param_1 + self.d_param_2)
            return r
        return _wrap

@dec8_c(2, 3)
def f8(a):
    print('call f8', a)

f8(1)
print(f8.__name__)

看看是不是实现了一样的效果:

2 3
<function f8 at 0x7fa1585f8048>
1
call f8 6
f8

虽然使用了 __call__ ,但这里的 __init__ 不能省略(因为它需要知道参数个数),否则会出现这个错误:

Traceback (most recent call last)
<ipython-input-276-1634a47057a2> in <module>
     14         return dec8
     15
---> 16 @dec8_c(2, 3)
     17 def f8(a):
     18     print('call f8', a)

TypeError: dec8_c() takes no arguments

同时还可以发现, __call__ 只需要两层函数了,去掉了第二层,直接把 _wrap 的函数体往上提了一层!

装饰器类(带参数的函数,不带参数的装饰器)

大概是吃饱了撑着,又想要实现一开始那个不带参数的装饰器了,那就继续敲敲打打一番看看:

class dec9_c:
    def  __init__(self, func):
        print(func)
        self.func = func
        self.__name__ = func.__name__
    def  __call__(self, param):
        print(param)
        func = self.func
        r = func(param)
        return r

@dec9_c
def f9(a):
    print('call f9', a)

f9(1)
print(f9.__name__)

赶快运行看看:

<function f9 at 0x7fa1585f8730>
1
call f9 1
f9

咦, f9 的函数名可以直接打印,这下都不用 @wraps 了呢!呃,再仔细看看,这写法好像有些不一样啊:

  • dec8_c 的 init 带的是装饰器的参数,可是 dec9_c 带的是装饰器函数自己!
  • 所以实际调用的函数名也可以在 init 中传给它了哦!
  • 而且 call 函数也简洁了很多,看来有没有参数真的有很大区别呢!

这里先做个总结,装饰器使用函数名形式(不带括号)和使用函数调用形式(带括号和参数)在实现上是不同的,因为前者是函数本身,而后者是从装饰器函数中返回的函数。这也是 f2 相比 f1 缺少了记录 after run 的原因,因为 dec1 直接调用了 f2 ,而 dec2 先运行得到函数,再把函数返回去调用 f2 。用装饰器类就可以解决这个问题,因为它是对 __call__ 的调用,只需要自己定义一下就可以了。

上面的 f9 要写两个函数,能不能写得和 f1 一样简洁?当然是可以的,使用 __new__ 大法:

from functools import wraps

class dec9x_c:
    def  __new__(self, func):
        print(func)
        @wraps(func)
        def dec9x(param):
            print(param)
            r = func(param)
            return r
        return dec9x

@dec9x_c
def f9x(a):
    print('call f9x', a)

f9x(1)
print(f9x.__name__)

这样就避开了函数调用,不用打call了(定义 __call__ 函数),快看它来了:

<function f9x at 0x7fa158623bf8>
1
call f9x 1
f9x

5 分钟全面掌握 Python 装饰器的更多相关文章

  1. 五分钟学会Python装饰器,看完面试不再慌

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是Python专题的第12篇文章,我们来看看Python装饰器. 一段囧事 差不多五年前面试的时候,我就领教过它的重要性.那时候我Pyt ...

  2. Python 装饰器入门(上)

    翻译前想说的话: 这是一篇介绍python装饰器的文章,对比之前看到的类似介绍装饰器的文章,个人认为无人可出其右,文章由浅到深,由函数介绍到装饰器的高级应用,每个介绍必有例子说明.文章太长,看完原文后 ...

  3. (转)python装饰器进阶一

    Python装饰器进阶之一 先看例子 网上有很多装饰器的文章,上来说半天也没让人看明白装饰器到底是个什么,究竟有什么用,我们直接来看几个例子. Python递归求斐波那契数列 def fibonacc ...

  4. 关于python装饰器

    关于python装饰器,不是系统的介绍,只是说一下某些问题 1 首先了解变量作用于非常重要 2 其次要了解闭包 def logger(func): def inner(*args, **kwargs) ...

  5. python装饰器通俗易懂的解释!

    1.python装饰器 刚刚接触python的装饰器,简直懵逼了,直接不懂什么意思啊有木有,自己都忘了走了多少遍Debug,查了多少遍资料,猜有点点开始明白了.总结了一下解释得比较好的,通俗易懂的来说 ...

  6. Python 装饰器学习

    Python装饰器学习(九步入门)   这是在Python学习小组上介绍的内容,现学现卖.多练习是好的学习方式. 第一步:最简单的函数,准备附加额外功能 1 2 3 4 5 6 7 8 # -*- c ...

  7. python 装饰器修改调整函数参数

    简单记录一下利用python装饰器来调整函数的方法.现在有个需求:参数line范围为1-16,要求把9-16的范围转化为1-8,即9对应1,10对应2,...,16对应8. 下面是例子: def fo ...

  8. python 装饰器学习(decorator)

    最近看到有个装饰器的例子,没看懂, #!/usr/bin/python class decorator(object): def __init__(self,f): print "initi ...

  9. Python装饰器详解

    python中的装饰器是一个用得非常多的东西,我们可以把一些特定的方法.通用的方法写成一个个装饰器,这就为调用这些方法提供一个非常大的便利,如此提高我们代码的可读性以及简洁性,以及可扩展性. 在学习p ...

随机推荐

  1. Android-寒假学习-阶段总结(20集)-口算测试APP

    说在前面: 1.视频教程:https://www.bilibili.com/video/av60445113/?spm_id_from=333.788.videocard.0 2.老师的源码:http ...

  2. UVA - 11584 Partitioning by Palindromes(划分成回文串)(dp)

    题意:输入一个由小写字母组成的字符串,你的任务是把它划分成尽量少的回文串,字符串长度不超过1000. 分析: 1.dp[i]为字符0~i划分成的最小回文串的个数. 2.dp[j] = Min(dp[j ...

  3. Mac安装vue产生错误

    npm WARN checkPermissions Missing write access to /usr/local/lib/node_modules/webpack/node_modules/_ ...

  4. Java8 Optional类使用小结

    Optional类的Javadoc描述如下: 这是一个可以为null的容器对象.如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象. of:  为非null的值创建一 ...

  5. SQL left join、rignt join、inner join区别

    说真的一直对数据库没有研究以至于连这些基础都不会了,事实证明,业精于勤荒于嬉. 废话不多说,直接开始 有A.B两张表: 1.inner join inner join 和 join 是没有区别的(如有 ...

  6. UVA_11525 树状数组的活用 二分

    我们知道1——k有K!种排列,现在给定k和n,要你按字典序输出 第n种排列的数列 而且题目给的 n是 n=S1(k-1)!+S2(k-2)!+...+Sk-1*1!+Sk*0!(0=<Si< ...

  7. 在Linux下 MySQL错误 ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES) 解决办法【很管用】

    一般这个错误是由密码错误引起,解决的办法自然就是重置密码. 假设我们使用的是root账户. 1.重置密码的第一步就是跳过MySQL的密码认证过程,方法如下: #vim /etc/my.cnf(注:wi ...

  8. Codeforces 1291A - Even But Not Even

    题目大意: 给定一个字符串数字(很大) 问能不能删除一些数字(或者不删除) 使得剩余的数字各位数相加是偶数,但是这整个数字是个奇数 解题思路: 统计字符串中单个数字奇数的个数 分情况 个数为0或者1时 ...

  9. html_js

    <!-- js的特点:别名脚本 -由浏览器内置的JavaScript引擎执行代码. -解析执行:事先不编译,逐行执行 -面向对象:内置大量的现成对象 适宜: -客户端的数据计算:不需要保存和提交 ...

  10. 9.1hadoop 内置计数器、自定义枚举计数器、Streaming计数器

    1.1  计数器 计数器的作用是用来统计数量的,用于记录特定事件的次数,分为内置计数器.自定义java枚举计数器.自定义Stream计数器三大类.用于质量分析,或应用级统计.分析计数器的值比分析一堆日 ...