资讯专栏INFORMATION COLUMN

Python Decorator的来龙

frank_fun / 3514人阅读

摘要:中的函数也是对象,可以作为高阶函数的参数传入或返回值返回。因此,当代理装饰的对象是函数时,可以使用高阶函数来对某个函数进行封装。

引言

本文主要梳理了Python decorator的实现思路,解释了为什么Python decorator是现在这个样子。

关于代理模式、装饰模式

设计模式中经常提到的代理模式、装饰模式,这两种叫法实际上是说的同一件事,只是侧重点有所不同而已。

这两者都是通过在原有对象的基础上封装一层对象,通过调用封装后的对象而不是原来的对象来实现代理/装饰的目的

例如:(以Java为例)

public class CountProxy implements Count {
    private CountImpl countImpl;

    public CountProxy(CountImpl countImpl) {
        this.countImpl = countImpl;
    }

    @Override
    public void queryCount() {  
        System.out.println("事务处理之前");
        // 调用委托类的方法;
        countImpl.queryCount();
        System.out.println("事务处理之后");
    }

    @Override
    public void updateCount() {
        System.out.println("事务处理之前");
        // 调用委托类的方法;
        countImpl.updateCount();
        System.out.println("事务处理之后");

    }

}

在这个例子中CountProxy是对CountImpl的封装。
使用者通过CountProxy.queryCount方法来调用CountImpl.queryCount方法,这被称为代理,即CountProxy是代理类,CountImpl是被代理类。
CountProxy.queryCount方法中,可以在CountImpl.queryCount方法调用之前和之后添加一些额外的操作,被称为装饰,即CountProxy是装饰类,CountImpl是被装饰类。

如果强调通过CountProxyCountImpl进行代理的作用,则称为代理模式;
如果强调通过CountProxyCountImpl增加额外的操作,则称为装饰模式;

不论是哪种称呼,其本质都在于对原有对象的封装。
其封装的目的在于增强所封装对象的功能或管理所封装的对象。

从上面的例子也可以发现,代理/封装所围绕的核心是可调用对象(比如函数)。

Python中的代理/装饰

Python中的可调用对象包括函数、方法、实现了__call__方法的类。
Python中的函数也是对象,可以作为高阶函数的参数传入或返回值返回。
因此,当代理/装饰的对象是函数时,可以使用高阶函数来对某个函数进行封装。
例如:

def query_count_proxy(fun, name, age):
    print("do something before")
    rv = fun(name, age)
    print("do something after")
    return rv


def query_count(name, age):
    print("name is %s, age is %d" % (name, age))


query_count_proxy(query_count, "Lee", 20)

但是,这个例子中,query_count函数作为参数传入query_count_proxy函数中,并在query_count_proxy函数中被调用,其结果作为返回值返回。这就完成了代理的功能,同时,在调用query_count函数的前后,我们还增加了装饰代码。
但是,query_count_proxy的函数参数与query_count不一样了,理想的代理应该保持接口一致才对。

为了保持一致,我们可以利用高阶函数可以返回函数的特点来完成:

def query_count_proxy(fun):

    def wrapper(name, age):
        print("do something before")
        rv = fun(name, age)
        print("do something after")
        return rv

    return wrapper


def query_count(name, age):
    print("name is %s, age is %d" % (name, age))


query_count_proxy(query_count)("Lee", 20)

修改后的例子,query_count_proxy仅负责接受被代理的函数query_count作为参数,同时,返回一个函数对象wrapper作为返回值,真正的封装动作在wrapper这个函数中完成。

此时,如果调用query_count_proxy(query_count)就得到了wrapper函数对象,则,执行query_count_proxy(query_count)("Lee", 20)就相当于执行了wrapper("Lee", 20)

但是可以看到,query_count_proxy(query_count)("Lee", 20)这种使用方法,仍然不能保证一致。

为了保持一致,我们需要利用Python中对象与其名称可以动态绑定的特点。
不使用query_count_proxy(quer_count)("Lee", 20)来调用代理函数,而是使用下面两句:

query_count = query_count_proxy(query_count)
query_count("Lee", 20)

执行query_count_proxy(query_count)生成wrapper函数对象,将这个对象通过query_count = query_count_proxy(query_count)绑定到query_count这个名字上来,这样执行query_count("Lee", 20)时,其实执行的是wrapper("Lee", 20)

这么做的结果就是:使用代理时调用query_count("Lee", 20)与不使用代理时调用query_count("Lee", 20)对使用者而言保持不变,不用改变代码,但是在真正执行时,使用的是代理/装饰后的函数。

这里,基本利用Python的高阶函数及名称绑定完成了代理/装饰的功能。
还有什么不理想的地方呢?
对,就是query_count = query_count_proxy(query_count),因为这句既不简洁,又属于重复工作。
Python为我们提供了语法糖来完成这类的tedious work。
方法就是:

@query_count_proxy
def query_count(name, age):
    return "name is %s, age is %d" % (name, age)

query_count = query_count_proxy(query_count)就等同于在定义query_count函数的时候,在其前面加上@query_count_proxy

Python看到这样的语法,就会自动的执行query_count = query_count_proxy(query_count)进行name rebinding

补充

以上就是Python实现可调用对象装饰的核心。
可调用对象包括函数、方法、实现了__call__方法的类,上述内容只是针对函数来解释,对于方法、实现了__call__方法的类,其基本原理相同,具体实现略有差别。

本文系作者原创,如有转载请注明出处。
由于水平精力有限,如有错误欢迎指正。

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

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

相关文章

  • Python知识点:理解和使用装饰器 @decorator

    摘要:使用类装饰器,优点是灵活性大,高内聚,封装性。不过不用担心,有,本身也是一个装饰器,它的作用就是把原函数的元信息拷贝到装饰器函数中,使得装饰器函数也有和原函数一样的元信息。 showImg(https://segmentfault.com/img/bVbrFWb?w=742&h=484);Python的装饰器(decorator)是一个很棒的机制,也是熟练运用Python的必杀技之一。...

    cyqian 评论0 收藏0
  • Python函数修饰器---当方法前遇到@参数化修饰器方法时发生

    一、前提概念   Python中的函数是对象。也因此,函数可以被当做变量使用。 二、代码模型 以下代码片段来自于: http://www.sharejs.com/codes/python/8361 # -*- coding: utf-8 -*- from threading import Thread import time class TimeoutEx...

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

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

    NeverSayNever 评论0 收藏0
  • Python 装饰器执行顺序迷思

    摘要:探究多个装饰器执行顺序装饰器是用于封装函数或代码的工具,网上可以搜到很多文章可以学习,我在这里要讨论的是多个装饰器执行顺序的一个迷思。这时候你该知道为什么输出结果会是那样,以及对装饰器执行顺序实际发生了什么有一定了解了吧。 探究多个装饰器执行顺序 装饰器是Python用于封装函数或代码的工具,网上可以搜到很多文章可以学习,我在这里要讨论的是多个装饰器执行顺序的一个迷思。 疑问 大部...

    frolc 评论0 收藏0
  • python 装饰器 part2

    摘要:装饰器传参被装饰的函数带有参数的情况接上一篇,直接上代码函数也就是被装饰的函数的运行时间是装饰器的正确使用,不需要传参装饰器的正确使用,需要传参此时不用再像上面一样赋值,可以直接调用返回值被装饰的函数有返回值在装饰器内部需被装饰函数的调用 python 装饰器 传参 被装饰的函数带有参数的情况 接上一篇,直接上代码 import time def decorator(func): ...

    sanyang 评论0 收藏0

发表评论

0条评论

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