爬虫 (二十七) Python Decorator(装饰器) (十八)

长文预警  阅读约5分钟 

相信如果你慢慢把这篇文章读完,然后合并实践,你重组python函数会有很大的理解,加油看完哦

经过上述三节,穿插讲了一下HTTP的知识点,以及浏览器的界面资源的获取,以及运行过程,我们可以收获到很多东西,如果没有好好看的伙伴可以趁热打铁

客户端浏览器一次http完整请求过程流程图(图文结合诠释请求过程)

网络基础HTTP协议进化篇

网络基础意淫篇

今天来说说 Python 里的装饰器 (decorator)。它不难,但却几乎是 “精通” Python 的路上的第一道关卡。让我们来看看它到底是什么东西,为什么我们需要它。

手写装饰器

现在我们要写一个函数:

def add(x, y=10):
    return x + y

然后我们想看看运行的结果,于是写了几个 print 语句:

print("add(10)",       add(10))
print("add(20, 30)",   add(20, 30))
print("add('a', 'b')", add('a', 'b'))

# Results:
# add(10) 20
# add(20, 30) 50
# add('a', 'b') ab

现在我们想看看测试这个函数的性能,于是我们加上这个代码:

from time import time

before = time()
print("add(10)",       add(10))
after = time()
print("time taken: {}".format(after - before))

before = time()
print("add(20, 30)",   add(20, 30))
after = time()
print("time taken: {}".format(after - before))

before = time()
print("add('a', 'b')", add('a', 'b'))
after = time()
print("time taken: {}".format(after - before))

# Results
# add(10) 20
# time taken: 0.00017189979553222656
# add(20, 30) 50
# time taken: 9.751319885253906e-05
# add('a', 'b') ab
# time taken: 0.00012969970703125

代码马上变得很复杂。但最重要的是,我们得写一堆代码(复制粘贴),程序员是懒惰的,所以我们就想到一些更简单的方法,与其写这么多次,我们可以只写一次代码:

from time import time
def add(x, y=10):
    before = time()
    result = x + y
    after = time()
    print('elapsed: ', after - before)
    return result

print("add(10)",       add(10))
print("add(20, 30)",   add(20, 30))
print("add('a', 'b')", add('a', 'b'))

# Results
# elapsed:  1.9073486328125e-06
# add(10) 20
# elapsed:  9.5367431640625e-07
# add(20, 30) 50
# elapsed:  1.9073486328125e-06
# add('a', 'b') ab

不论是代码的修改量还是代码的美观程度,都比之前的版本要好!

但是,现在我们写了另一个函数:

def sub(x, y=10):
    return x - y

我们必须再为 sub 函数加上和 add 相同的性能测试代码:

def sub(x, y=10):
    before = time()
    result = x - y
    after = time()
    print('elapsed: ', after - before)
    return result

作为一个懒惰的程序员,我们立马就发现了,有一个 “模式” 反复出现,即执行一个函数,并计算这个函数的执行时间。于是我们就可以把这个模式抽象出来,用函数:

from time import time

def timer(func, x, y = 10):
    before = time()
    result = func(x, y)
    after = time()
    print("elapsed: ", after - before)
    return result

def add(x, y = 10):
    return x + y

def sub(x, y = 10):
    return x - y

print("add(10)", timer(add, 10))
print("add(20, 30)", timer(add, 20, 30))

但这样还是很麻烦,因为我们得改到所有的测试用例,把 add(20, 30) 改成 timer(add, 20, 30)。于是我们进一步改进,让 timer 返回函数:

def timer(func):
    def wraper(x, y=10):
        before = time()
        result = func(x, y)
        after = time()
        print("elapsed: ", after - before)
        return result
    return wraper

def add(x, y = 10):
    return x + y
add = timer(add)

def sub(x, y = 10):
    return x - y
sub = timer(sub)

print("add(10)",       add(10))
print("add(20, 30)",   add(20, 30))

这里的最后一个问题是,我们的 timer 包装的函数可能有不同的参数,于是我们可以进一步用 *args, **kwargs 来传递参数:

def timer(func):
    def wraper(*args, **kwargs):
        before = time()
        result = func(*args, **kwargs)
        after = time()
        print("elapsed: ", after - before)
        return result
    return wraper

这里的 timer 函数就是一个 “装饰器”,它接受一个函数,并返回一个新的函数。在装饰器的内部,对原函数进行了“包装”。

注:上面的例子取自 What Does it Take to Be an Expert At Python。

@ 语法糖

上一节是一个懒惰的程序员用原生的 Python 写的装饰器,但在装饰器的使用上,用的是这个代码:

def add(x, y = 10):
    return x + y
add = timer(add)        # <- notice this

def sub(x, y = 10):
    return x - y
sub = timer(sub)

上面这个语句里,我们把 add 的名字重复了 3 次,如果函数改了名字,我们就得改 3 处。懒惰的程序员就想了一个更“好”的方法,提供了一个语法来替换上面的内容:

@timer
def add(x, y=10):
    return x + y

这就是我们最常见的装饰器的形式了,这两种写法完全等价,只是 @ 写法更简洁一些

带参数的装饰器

我们知道下面两种代码是等价的:

@dec
def func(...):
    ...

func = dec(func)

我们可以把它当成是纯文本的替换,于是可以是这样的:

@dec(arg)
def func(...):
    ...

func = dec(arg)(func)

这也就是我们看到的“带参数”的装饰器。可见,只要 dec(arg) 的返回值满足 “装饰器” 的定义即可。(接受一个函数,并返回一个新的函数)

这里举一个例子(来源):

def use_logging(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if level == "warn":
                logging.warn("%s is running" % func.__name__)
            elif level == "info":
                logging.info("%s is running" % func.__name__)
            return func(*args)
        return wrapper

    return decorator

@use_logging(level="warn")
def foo(name='foo'):
    print("i am %s" % name)

先不管 use_logging 长什么样,先关心它的返回值 decorator,看到 decorator 本身是一个函数,并且参数是函数,返回值是函数,于是确认 decorator 是一个 “装饰器”。于是上面这种“带参数的装饰器”的作用也就很直接了。

类作为装饰器

如果说 Python 里一切都是对象的话,那函数怎么表示成对象呢?其实只需要一个类实现 __call__ 方法即可。

class Timer:
    def __init__(self, func):
        self._func = func
    def __call__(self, *args, **kwargs):
        before = time()
        result = self._func(*args, **kwargs)
        after = time()
        print("elapsed: ", after - before)
        return result

@Timer
def add(x, y=10):
    return x + y

也就是说把类的构造函数当成了一个装饰器,它接受一个函数作为参数,并返回了一个对象,而由于对象实现了 __call__ 方法,因此返回的对象相当于返回了一个函数。因此该类的构造函数就是一个装饰器。

小结

装饰器中还有一些其它的话题,例如装饰器中元信息的丢失,如何在类及类的方法上使用装饰器等。但本文里我们主要目的是简单介绍装饰器的原因及一般的使用方法,能用上的地方就大胆地用上吧!

扩展阅读

  • [PEP 0318 – Decorators for Functions and Methods](PEP 0318 – Decorators for Functions and Methods)

  • Python Decorator in Detail

  • What Does it Take to Be an Expert At Python

  • 理解 Python 装饰器看这一篇就够了

  • How you implemented your Python decorator is wrong

请继续关注我

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
博客
v8worker
05-08 2883
05-06 2861
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值