资讯专栏INFORMATION COLUMN

python装饰器的原理和使用

goji / 1197人阅读

摘要:我们以测量函数运行时间为例来讲一讲装饰器的运行原理。三更加通用的装饰器前面两部分讲了装饰器的原理,这一部分就讲讲要写出一个通用的装饰器需要注意的问题。首先就是参数的问题,装饰器返回的函数不是原来的函数,函数的签名也就和原来的函数签名不一样。

一、最简单的装饰器

装饰器是python中很基础也很实用的一个特性。通过装饰器我们可以很方便地为一些函数添加相同的功能。我们以测量函数运行时间为例来讲一讲python装饰器的运行原理。

1、使用装饰器打印函数运行时间

通常我们使用 time 库来获取函数的运行时间。

import time

# 函数运行 2秒
def func():
    time.sleep(2)

start_time = time.time()
func()
end_time = time.time()
print("函数运行时间为:%.2fs" % (end_time - start_time)) 

so easy,现在我们来思考一下:如果我们有 100个函数,怎么打印这100个函数的运行时间呢?

总不能按照上面的写法来100次吧,这绝不是一个程序猿应该做的事,这时候装饰器就可以排上用场了。

我们先看代码,然后再慢慢讲其中的原理。

import time


def timeit(func):
    def result():
        start_time = time.time()
        func()
        end_time = time.time()
        print("函数运行时间为:%.2fs" % (end_time - start_time))
    return result

@timeit
def func_0():
    time.sleep(2)
    
# 省略98个

@timeit
def func_99():
    time.sleep(2)

# 接下来直接调用这100个函数即可
func_0()
# ...

使用了 timeit 装饰器的函数和没使用装饰器的原始方法效果一样,不过这只是装饰器最简单的一个应用,我们下面来看看其中的原理。

2、timeit的运行原理

首先,大家需要知道在python中的函数也是对象,是对象就可以作为参数传递,这是装饰器实现的基础。

接下来我们来看看装饰器 timeit,不难看出 timeit 是一个函数。准确地说 timeit 是一个接受一个函数为参数并且返回一个函数的函数。

听着挺拗口的,别急,看我细细道来。

timeit 是一个函数,它接受一个参数,这个参数必须是函数(或者说可调用对象),并且 timeit 的返回结果一定是一个函数。

看到这里大家应该对装饰器有一点了解了,原来装饰器就是接受一个函数为参数返回另一个函数的函数。

现在我们就可以解释 timeit 的运行原理了,看下面的代码

# 代码块1
@timeit
def func_0():
    time.sleep(2)
    
# 代码块2
def func_0():
    time.sleep(2)
func_0 = timeit(func_0)

这里的代码块1和代码块2完全等价,注意完全两个字,这说明这两段代码的效果一模一样。

事实上代码块1就是代码块2的简写形式,因为代码块2的写法挺麻烦的,所以python的设计者加了一些语法糖,就有了代码块1的写法。

了解了这些我们再来看看 timeit 内部

def timeit(func):
    def result():
        start_time = time.time()
        func()
        end_time = time.time()
        print("函数运行时间为:%.2fs" % (end_time - start_time))
    return result

timeit里面我们定义了一个函数 result ,函数 result 里面的内容我们很熟悉,就是打印函数 func 的运行时间。这里的 func 就是我们要装饰的函数。

最后我们将函数 result 返回,我们再看到代码块2,返回的 result 函数替换了我们要装饰的函数 func_0,这时当我们再调用函数 func_0 时其实就是在调用函数 result

3、什么是闭包

我们看一段代码:

def outer():
    a = 0
    def inner():
        print(a)
    return inner

func = outer()
func()

上面的函数和装饰器很像,以前学C语言的可能会疑惑为什么变量a在函数 outer 调用完成后仍然能够被访问。

这和python中变量的回收机制有关,python根据变量的引用计数来判断是变量是否需要回收。

当一个对象的引用被创建或复制时,对象的引用计数加1;当一个对象的引用被销毁时,对象的引用计数减1,如果对象的引用计数减少为0,将对象的所占用的内存释放。

上面的例子中因为对象a的引用一直没有被销毁引用计数不为0,所以在函数 inner 里一直可以访问对象a的值。

而且我们发现在调用过函数 outer 后只有函数 inner 可以访问对象a。

这就相当于为函数 inner添加了一个私有的命名空间,而且只有函数 inner 可以访问这个命名空间,这就是python中的闭包。

装饰器就是利用了闭包的原理,所以我们说装饰器就是是闭包也是完全没有问题的。

二、带参数的装饰器

我们可能看到过这样的装饰器

@decorator("arg","arg2")
def func():
    # do something
    pass

在一些代码中我们发现有些装饰器是有参数的,这样可以带参数的装饰器是怎么实现的呢?下面我就和大家讲一讲带参数的装饰器是怎么实现的。

在了解了装饰器的原理之后我们知道装饰器其实就是一个返回一个函数的函数。

于是我们就想:既然有返回函数的函数,那有没有返回装饰器的函数呢?当然是有的!

事实上,上面提到的带参数的装饰器就是一个返回装饰器的函数。

其中的原理也很简单,圆括号表示函数调用,而我们的程序都是从右到左执行的,所以在程序运行的时候 @ 后面的应该是 decorator 返回的装饰器。

说完原理,我们再来看看带参数的装饰器应该怎么写,先上代码。

import time


def timeit(out="函数运行时间为:%.2fs"):
    def decorator(func):
        def result():
            start_time = time.time()
            func()
            end_time = time.time()
            print(out % (end_time - start_time))
        return result
    return decorator

@timeit("函数运行时间为:%.2fs  --来自带参数的装饰器")
def func():
    time.sleep(2)
    
func() # => 函数运行时间为:2.00s  --来自带参数的装饰器

我们在原来打印函数运行时间的装饰器上添加了自定义打印语句的功能,在使用的时候和下面的代码等价。

func = timeit("函数运行时间为:%.2fs  --来自带参数的装饰器")(func)

带参数的装饰器也是利用了闭包将参数绑定到返回的函数上。

三、更加通用的装饰器

前面两部分讲了装饰器的原理,这一部分就讲讲要写出一个通用的装饰器需要注意的问题。

首先就是参数的问题,装饰器返回的函数不是原来的函数,函数的签名也就和原来的函数签名不一样。

当我们还是按照来的方式去调用函数就有可能会出问题,我们通过一个例子来看一下。

def decorator(func):
    def result(a):
        print(a)
        func()
    return result

@decorator
def func(a,b,c):
    print(a,b,c)
    
func(1, 2, 3)
# => TypeError: result() takes 1 positional argument but 3 were given

这里报错是因为装饰器返回的函数只接受一个参数,我们还按照调用 func 的方式来调用 result当然会报错。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/44065.html

相关文章

  • 简单理解Python装饰

    摘要:下面我们一起抛去无关概念,简单地理解下的装饰器。用函数实现装饰器装饰器要求入参是函数对象,返回值是函数对象,嵌套函数完全能胜任。为了对调用方透明,装饰器返回的对象要伪装成被装饰的函数。 来源:http://www.lightxue.com/under...        Python有大量强大又贴心的特性,如果要列个最受欢迎排行榜,那么装饰器绝对会在其中。       刚接触装饰器,会...

    Meils 评论0 收藏0
  • 详解Python装饰

    摘要:概括的讲,装饰器的作用就是为已经存在的函数或对象添加额外的功能。在理解这些装饰器之前,最好对函数的闭包和装饰器的接口约定有一定了解。是一个非常简单的装饰器加强包。 Python中的装饰器是你进入Python大门的一道坎,不管你跨不跨过去它都在那里。 为什么需要装饰器 我们假设你的程序实现了say_hello()和say_goodbye()两个函数。 def say_hello(): ...

    DandJ 评论0 收藏0
  • 利用世界杯,读懂 Python 装饰

    摘要:今天就结合最近的世界杯带大家理解下装饰器。而德国是上届的冠军,又是这届夺冠热门。装饰器的存在是为了适用两个场景,一个是增强被装饰函数的行为,另一个是代码重用。在利用语法糖,简化赋值操作。行为良好的装饰器可以重用,以减少代码量。 Python 装饰器是在面试过程高频被问到的问题,装饰器也是一个非常好用的特性,熟练掌握装饰器会让你的编程思路更加宽广,程序也更加 pythonic。 show...

    xiguadada 评论0 收藏0
  • Python】一文弄懂python装饰器(附源码例子)

    摘要:装饰器的使用符合了面向对象编程的开放封闭原则。三简单的装饰器基于上面的函数执行时间的需求,我们就手写一个简单的装饰器进行实现。函数体就是要实现装饰器的内容。类装饰器的实现是调用了类里面的函数。类装饰器的写法比我们装饰器函数的写法更加简单。 目录 前言 一、什么是装饰器 二、为什么要用装饰器 ...

    liuchengxu 评论0 收藏0
  • Python 装饰使用指南

    摘要:装饰器是可调用的对象,其参数是另一个函数被装饰的函数。第二大特性是,装饰器在加载模块时立即执行。另一个常见的装饰器是,它的作用是协助构建行为良好的装饰器。 装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。 装饰器基础知识 首先看一下这段代码 def deco(fn): print I am %s! % fn.__name__ @deco def func(): ...

    NeverSayNever 评论0 收藏0

发表评论

0条评论

goji

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<