python装饰器


题图来自Dobiasd的article项目聊到的Python的学习曲线,虽然作者说” The only purpose of this is to entertain. It has no empirical base whatsoever. “,但是我觉得还是需要重视文章中提到的一个说法:python掌握程度的评价标准中肯定包含装饰器。装饰器是python语言的语法糖,没有它并不会影响python编程,但作为评价标准之一,我想自己还是要能够回答下述几个问题:
1.Python装饰器有什么用?
2.Python装饰器是什么?
3.Python装饰器实现的原理是什么?
4.Python装饰器有哪几种具体形式?
5.Python装饰器有哪些应用场景?
6.Python装饰器在著名开源项目中的应用?

这就是这篇文章的由来。

1.Python装饰器有什么用?

我们假设你的程序实现了say_hello()say_goodbye()两个函数。

def say_hello():
    print "hello!"

def say_goodbye():
    print "hello!" # bug here

if __name__ == '__main__':
    say_hello()
    say_goodbye()

但是在实际调用中,我们发现程序出错了,上面的代码打印了两个hello。经过调试你发现是say_goodbye()出错了。老板要求调用每个方法前都要记录进入函数的名称,比如这样:

[DEBUG]: Enter say_hello()
Hello!
[DEBUG]: Enter say_goodbye()
Goodbye!

好,小A是个毕业生,他是这样实现的。

def say_hello():
    print "[DEBUG]: enter say_hello()"
    print "hello!"

def say_goodbye():
    print "[DEBUG]: enter say_goodbye()"
    print "hello!"

if __name__ == '__main__':
    say_hello()
    say_goodbye()

很low吧? 嗯是的。小B工作有一段时间了,他告诉小A可以这样写。

def debug():
    import inspect
    caller_name = inspect.stack()[1][3]
    print "[DEBUG]: enter {}()".format(caller_name)

def say_hello():
    debug()
    print "hello!"

def say_goodbye():
    debug()
    print "goodbye!"

if __name__ == '__main__':
    say_hello()
    say_goodbye()

是不是好一点?那当然,但是每个业务函数里都要调用一下debug()函数,是不是很难受?万一老板说say相关的函数不用debug,do相关的才需要呢?那么装饰器这时候应该登场了。

2.Python装饰器是什么?

最早的时候Python 2.2版本引入为对象模型加入了static methods和class methods(早期的装饰器),而装饰器(Decorator)的概念是在Python 2.4版本作为新的语言特性正式引入Python的,Python 2.6版本对将函数装饰器扩展到类装饰器(同时包括了getter, setter and deleter)。

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的函数或对象添加额外的功能。

3.Python装饰器实现的原理是什么?

3.1闭包

在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。用比较容易懂的人话说,就是当某个函数被当成对象返回时,夹带了外部变量,就形成了一个闭包,一般支持函数式编程的语言都支持闭包。看例子:

def make_printer(msg):
    def printer():
        print msg # 夹带私货(外部变量)
    return printer # 返回的是函数,带私货的函数

printer = make_printer('Foo!')
printer()

闭包存在的意义就是它夹带了外部变量(私货),如果它不夹带私货,它和普通的函数就没有任何区别。同一个的函数夹带了不同的私货,就实现了不同的功能。其实你也可以这么理解,闭包和面向接口编程的概念很像,可以把闭包理解成轻量级的接口封装。

def tag(tag_name):
    def add_tag(content):
        return "<{0}>{1}</{0}>".format(tag_name, content)
    return add_tag

content = 'Hello'

add_tag = tag('a')
print add_tag(content)
# <a>Hello</a>

add_tag = tag('b')
print add_tag(content)
# <b>Hello</b>

在这个例子里,我们想要一个给contenttag的功能,但是具体的tag_name是什么样子的要根据实际需求来定,对外部调用的接口已经确定,就是add_tag(content)。如果按照面向接口方式实现,我们会先把add_tag写成接口,指定其参数和返回类型,然后分别去实现a和b的add_tag

但是在闭包的概念中,add_tag就是一个函数,它需要tag_namecontent两个参数,只不过tag_name这个参数是打包带走的。所以一开始时就可以告诉我怎么打包,然后带走就行。

上面的例子不太生动,其实在我们生活和工作中,闭包的概念也很常见。比如说手机拨号,你只关心电话打给谁,而不会去纠结每个品牌的手机是怎么实现的,用到了哪些模块。再比如去餐馆吃饭,你只要付钱就可以享受到服务,你并不知道那桌饭菜用了多少地沟油。这些都可以看成闭包,返回来的是一些功能或者服务(打电话,用餐),但是这些功能使用了外部变量(天线,地沟油等等)。

你也可以把一个类实例看成闭包,当你在构造这个类时,使用了不同的参数,这些参数就是闭包里的包,这个类对外提供的方法就是闭包的功能。但是类远远大于闭包,因为闭包只是一个可以执行的函数,但是类实例则有可能提供很多方法。

下面让我们来了解一下闭包的包到底长什么样子。其实闭包函数相对与普通函数会多出一个__closure__的属性,里面定义了一个元组用于存放所有的cell对象,每个cell对象一一保存了这个闭包中所有的外部变量。

>>> def make_printer(msg1, msg2):
        def printer():
            print msg1, msg2
        return printer
>>> printer = make_printer('Foo', 'Bar') # 形成闭包

>>> printer.__closure__ # 返回cell元组
(, )

>>> printer.__closure__[0].cell_contents # 第一个外部变量
'Foo'
>>> printer.__closure__[1].cell_contents # 第二个外部变量
'Bar'

3.2装饰器接口

因为Python的装饰器是一个固定的函数接口形式。它要求你的装饰器函数(或装饰器类)必须接受一个函数并返回一个函数:


# how to define
def wrapper(func1): # 接受一个callable对象
    return func2 # 返回一个对象,一般为函数

# how to use
def target_func(args): # 目标函数
    pass

# 调用方式一,直接包裹
result = wrapper(target_func)(args)

# 调用方式二,使用@语法,等同于方式一
@wrapper
def target_func(args):
    pass

result = target_func()

那么如果你的装饰器如果带参数呢?那么你就需要在原来的装饰器上再包一层,用于接收这些参数。这些参数(私货)传递到内层的装饰器里后,闭包就形成了。所以说当你的装饰器需要自定义参数时,一般都会形成闭包。(类装饰器例外)

4.Python装饰器有哪几种具体形式?

# 1. simple decorator
def debug(func):
    def wrapper(*args, **kwargs):
        print("[DEBUG]: enter {}()".format(func.__name__))
        return func(*args, **kwargs)
    return wrapper

@debug
def say_hello(msg):
    print('hello {}!'.format(msg))

@debug
def say_goodbye(msg):
    print('goodbye {}'.format(msg))

# 2. decorator with parameter
def log(level):
    def wrapper(func):
        def inner_wrapper(*args, **kwargs):
            print('[{level}]: enter function {func}()'.format(
                    level=level,
                    func=func.__name__))
            return func(*args, **kwargs)
        return inner_wrapper
    return wrapper

@log(level='INFO')
def say_hello_log(msg):
    print('logging\nhello {}'.format(msg))

@log(level='WARNING')
def say_goodbye_log(msg):
    print('logging\ngoodbye {}'.format(msg))

# 3. simple class decorator
class logs(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("[DEBUG]: enter func {}".format(self.func.__name__))
        return self.func(*args, **kwargs)

    @logs
    def say_str(msg):
        print('hi {}, I am done!'.format(msg))

# 4. class decorator with parameter
class logging(object):
    def __init__(self, level="INFO"):
        self.level = level

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print("[{level}]: enter function {func}()".format(
                    level = self.level,
                    func = func.__name__))
            return func(*args, **kwargs)
        return wrapper

@logging(level='ERROR')
def say(msg):
    print("edony say {}!".format(msg))

# 5. inner decorator
## property
class pro(object):
    def __init__(self, x):
        self._x = x

    def getx(self):
        return self._x

    def setx(self, value):
        self._x = value

    def delx(self):
        del self._x

x = property(getx, setx, delx, "I am a monkey")
class pro2(object):
    def __init__(self, x):
        self._x = x
    @property
    def getx(self):
        'I am a monkey'
        return self._x
    @getx.setter
    def setx(self, value):
        self._x = value
    @getx.deleter
    def delx(self):
        del self._x

if __name__ == "__main__":
    say_hello('edony')
    say_goodbye('edony')
    say_hello_log('EDONY')
    say_goodbye_log('EDONY')
    say_str('bob')
    say("WTF, I made a mistake again!!!")
    pro_tmp = pro(1)
    print(pro_tmp.x)
    pro_tmp.x = 12.111
    print(pro_tmp.x)
    help(pro.x)
    del(pro_tmp.x)
    pro2_tmp = pro2(2222)
    pro2_tmp.x = 12
    print(pro2_tmp.x)
    help(pro2.getx)

5.Python装饰器有哪些应用场景?

Python官方的装饰器实例列表,你可以在里边看到装饰器的各种妙用PythonDecoratorLibrary

总结起来有这么几类(后续会有文章针对每种用法从源码进行详细的分析总结):
1. 注入参数(提供默认参数,生成参数)
2. 记录函数行为(日志、缓存、计时什么的)
3. 预处理/后处理(配置上下文什么的)
4. 修改调用时的上下文(线程异步或者并行,类方法)

6.Python装饰器在著名开源项目中的应用?

1.requests


[edony@edony requests-2.18.1:32]$ grep -rn "^\s\{0,\}@.*" `find . -name "*.py" -type f` | wc -l
97
[edony@edony requests-2.18.1:33]$ grep -rn "^\s\{0,\}@.*" `find . -name "*.py" -type f`
./requests/cookies.py:85: @property
./requests/cookies.py:89: @property
./requests/cookies.py:93: @property
./requests/models.py:61: @property
./requests/models.py:82: @staticmethod
./requests/models.py:109: @staticmethod
./requests/models.py:337: @staticmethod
./requests/models.py:689: @property
./requests/models.py:704: @property
./requests/models.py:711: @property
./requests/models.py:716: @property
./requests/models.py:721: @property
./requests/models.py:812: @property
./requests/models.py:832: @property

 

2.flask

[edony@edony flask-0.12.2:35]$ grep -rn "^\s\{0,\}@.*" `find . -name "*.py" -type f` | wc -l
422
[edony@edony flask-0.12.2:36]$ grep -rn "^\s\{0,\}@.*" `find . -name "*.py" -type f`
./examples/blueprintexample/simple_page/simple_page.py:7:@simple_page.route('/', defaults={'page': 'index'})
./examples/blueprintexample/simple_page/simple_page.py:8:@simple_page.route('/<page>')
./examples/blueprintexample/test_blueprintexample.py:13:@pytest.fixture
./examples/flaskr/flaskr/flaskr.py:48:@app.cli.command('initdb')
./examples/flaskr/flaskr/flaskr.py:64:@app.teardown_appcontext
./examples/flaskr/flaskr/flaskr.py:71:@app.route('/')
./examples/flaskr/flaskr/flaskr.py:79:@app.route('/add', methods=['POST'])
./examples/flaskr/flaskr/flaskr.py:91:@app.route('/login', methods=['GET', 'POST'])
./examples/flaskr/flaskr/flaskr.py:106:@app.route('/logout')
./examples/flaskr/tests/test_flaskr.py:18:@pytest.fixture
./examples/jqueryexample/jqueryexample.py:15:@app.route('/_add_numbers')
./examples/jqueryexample/jqueryexample.py:23:@app.route('/')
./examples/minitwit/minitwit/minitwit.py:44:@app.teardown_appcontext
./examples/minitwit/minitwit/minitwit.py:60:@app.cli.command('initdb')
./examples/minitwit/minitwit/minitwit.py:92:@app.before_request
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s