Skip to content

Instantly share code, notes, and snippets.

@chzyer
Last active December 10, 2015 23:19
Show Gist options
  • Save chzyer/4508459 to your computer and use it in GitHub Desktop.
Save chzyer/4508459 to your computer and use it in GitHub Desktop.
装饰器和功能性python

原文: http://www.brianholdefehr.com/decorators-and-functional-python

装饰器是python的一大特色. 除了他在语言中非常实用之外, 还能够帮助我们通过一个有趣的方式来思考 -- 功能性
我会从头开始解释装饰器是怎么工作的. 我们会从一些你理解装饰器所必须的话题开始. 然后, 我们再去深入得探究几个简单的装饰器的工作原理. 最后, 我们会讨论一些装饰器的高级用法, 比如传入可选性的参数, 同时使用多个装饰器等等.

首先, 让我们用我能想到的最简单的方法来定义Python的函数. 根据这个定义, 我们可以用简单类似的方法来定义装饰器.

A function is a block of reusable code that performs a specific task.

不错, 然后什么又是装饰器呢?

A decorator is a function that modifies other functions.

现在让我们开始扩充装饰器的定义, 以一段必要的解释作为开头.

###函数是第一级对象(first class objects)

在python中, 一切皆为对象. 这就意味着函数可以被变量指向, 可以像其他对象一样传递. 比如:

def traveling_function():
    print "Here I am!"

function_dict = {
    "func": traveling_function
}

trav_func = function_dict['func']
trav_func()
# >> Here I am!

function_dictfunc 值指向了traveling_function, 并且traveling_function 还可以像通常那样被调用

###第一级函数可以被上级函数访问

我们完全可以像传递其他对象一样传递函数. 我们可以把他们当做dict的值去传递, 或者把他们放到list里面, 或者指派他们为对象的属性. 所以难道我们还不能把他们当做另一个函数的参数传递过去吗? 当然可以! 一个函数可以通过参数的方式接收其他函数, 也可以返回另一个参数给调用他的上级函数.

def self_absorbed_function():
    return "I'm an amazing function!"


def printer(func):
    print "The function passed to me says: " + func()

# Call `printer` and give it `self_absorbed_function` as an argument
printer(self_absorbed_function)
# >> The function passed to me says: I'm an amazing function!

正如上面你所看到的, 一个函数可以当做参数传递给其他函数, 并且函数还可以调用这个传递进来的函数. 这就意味着我们可以创建一些有趣的函数, 比如装饰器!

###装饰器的基础

我认为, 一个装饰器只是一个函数, 只不过他可以从参数拿到另一个函数. 在很多情况下, 他们返回一个经过修改并且被藏在内部的函数. 让我们找一找最简单的但又能帮助我们理解他全部工作原理的装饰器 - 身份装饰器

def identity_decorator(func):
    def wrapper():
        func()
    return wrapper

def a_function():
    print "I'm a normal function."

# `decorated_function` is the function that `identity_decorator` returns, which
# is the nested function, `wrapper`
decorated_function = identity_decorator(a_function)

# This calls the function that `identity_decorator` returned
decorated_function()
# >> I'm a normal function

这里, 总的来说identity_decorator并没有修改他内置的函数. 他只是简单的返回一个函数(wrapper), 当他被调用的时候, 会调用identity_decorator最开始接收到的的函数(译者注: a_function). 呵呵, 其实这个装饰器一点用处都没有.

有个有趣的地方, 看看identity_decorator, wrapper并没有被传递任何参数, 但是竟然还能够访问到func. 这个嘛, 要归功于闭包.

###闭包

Closure is a fancy term meaning that when a function is declared, it maintains a reference to the lexical environment in which it was declared.

在上个例子中, 当wrapper被定义后, 他可以在他的作用域内访问变量func. 这意味着在整个wrapper的生命周期(被返回并且指向了变量decorated_function之后)内, 他可以访问到变量func. 一旦identity_decorator返回了, 要访问func的方式只能通过decorated_function. func 只是以变量的形式存在于decorated_function的闭包环境.

###一个简单的装饰器

接下来让我们写一个稍微有点用处的装饰器吧. 整个装饰器的用处是记录这个被修改的函数被访问的次数.

def logging_decorator(func):
    def wrapper():
        wrapper.count += 1
        print "The function I modify has been called {0} times(s).".format(
              wrapper.count)
        func()
    wrapper.count = 0
    return wrapper

def a_function():
    print "I'm a normal function."

modified_function = logging_decorator(a_function)

modified_function()
# >> The function I modify has been called 1 time(s).
# >> I'm a normal function.

modified_function()
# >> The function I modify has been called 2 time(s).
# >> I'm a normal function.

我们通常说一个装饰器修改了一个函数, 这个有利于我们顺着装饰器的思路去思考. 但是正如我们的例子你所看到的, logging_decorator 只是返回一个新的函数, 这个函数和 a_function很相近, 只不过加上了日志的功能.

在这里例子中, logging_decorator 不仅仅是接收一个函数参数, 他还返回一个函数, wrapper. 当logging_decorator每次被调用的时候, 它会递增wrapper.count, 并且打印出来, 实际上调用logging_decorator实际上是包装过的.

你应该有个疑问, 为什么我们的计数器是wrapper的属性, 而不是一个常规的变量? 不能在wrapper提供的闭包环境直接声明变量吗? 是的, 但是有一个问题. 在Python里, 一个闭包提供在函数的作用域内所有变量的读权限, 对于写权限, 只有可变对象(mutable objects)例如list, dict, 等等才有. 一个整型在python里面算是不可变对象(immutable objects), 所以我们不能在wrapper里面递增他的值. 作为解决方案, 我们把计数器当做是wrapper的一个属性, 一个可变对象, 所以后来我们可以随意的增加他的值了.

###装饰器的语法

在上一个例子中, 我们可以知道一个装饰器可以使用传递给他的函数参数, 经过'包装'之后, 函数变为了装饰器函数. 然而, Python还提供一个语法特性, 让装饰器更加的直观和易读.

# In the previous example, we used our decorator function by passing the
# function we wanted to modify to it, and assigning the result to a variable

def some_function():
    print "I'm happiest when decorated."
# Here we will make the assigned variable the same name as the wrapped function
some_function = logging_decorator(some_function)
# We can achieve the exact same thing with this syntax:

@logging_decorator
def some_function():
    print "I'm happiest when decorated."

只用装饰器的语法之后, 我们可以从鸟瞰的角度看看发生了什么事情:

翻译未完, 续待

@chzyer
Copy link
Author

chzyer commented Jan 13, 2013

评论测试!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment