Python Tricks - Effective Functions(2)

The Power of Decorators

At their core, Python’s decorators allow you to extend and modify the behavior of a callable (functions, methods, and classes) without permanently modifying the callable itself.

装饰器可以拓展和修改调用性,而不需要永久性修改函数。

Any sufficiently generic functionality you can tack on to an existing
class or function’s behavior makes a great use case for decoration.
This includes the following:

  • logging
  • enforcing access control and authentication
  • instrumentation and timing functions
  • rate-limiting
  • caching, and more

装饰器可以用于:日志记录,访问控制和身份认证,时间测量功能,速率控制,缓存

Now, why should you master the use of decorators in Python? After all, what I just mentioned sounded quite abstract, and it might be difficult to see how decorators can benefit you in your day-to-day work as a Python developer. Let me try to bring some clarity to this question by giving you a somewhat real-world example:

开始要举例子了。

Imagine you’ve got 30 functions with business logic in your reportgenerating program. One rainy Monday morning your boss walks up to your desk and says: “Happy Monday! Remember those TPS reports? I need you to add input/output logging to each step in the report generator. XYZ Corp needs it for auditing purposes. Oh, and I told them we can ship this by Wednesday.”

ship this 搞定这个玩意。

Depending on whether or not you’ve got a solid grasp on Python’s decorators, this request will either send your blood pressure spiking or leave you relatively calm.

根据你对python语言的掌握程度,这段内容让你血压上升或者让你相对镇定。

Without decorators you might be spending the next three days scrambling to modify each of those 30 functions and clutter them up with manual logging calls. Fun times, right?

欢乐时光开始啦!

If you do know your decorators however, you’ll calmly smile at your
boss and say: “Don’t worry Jim, I’ll get it done by 2pm today.”

Right after that you’ll type the code for a generic @audit_log decorator (that’s only about 10 lines long) and quickly paste it in front of each function definition. Then you’ll commit your code and grab another cup of coffee…

创建好装饰器然后就可以喝卡布其诺了。

I’m dramatizing here, but only a little. Decorators can be that powerful. I’d go as far as to say that understanding decorators is a milestone for any serious Python programmer. They require a solid grasp of several advanced concepts in the language, including the properties of first-class functions.

understanding how decorators work in Python can be enormous

Sure, decorators are relatively complicated to wrap your head around for the first time, but they’re a highly useful feature that you’ll often encounter in third-party frameworks and the Python standard library. Explaining decorators is also a make or break moment for any good Python tutorial. I’ll do my best here to introduce you to them step by step.

解释好装饰器是决定是否是优秀的python教程的成败之处。

Before you dive in however, now would be an excellent moment to refresh your memory on the properties of first-class functions in Python. There’s a chapter on them in this book, and I would encourage you to take a few minutes to review it. The most important “first-class functions” takeaways for understanding decorators are:

  • Functions are objects—they can be assigned to variables and passed to and returned from other functions
  • Functions can be defined inside other functions—and a child function can capture the parent function’s local state (lexical closures)

回顾一下,函数都是对象,函数可以在另外一个函数中定义。

Alright, are you ready to do this? Let’s get started.

发车了......

Python Decorator Basics

Now, what are decorators really? They “decorate” or “wrap” another function and let you execute code before and after the wrapped function runs.

Decorators allow you to define reusable building blocks that can change or extend the behavior of other functions. And, they let you do that without permanently modifying the wrapped function itself. The function’s behavior changes only when it’s decorated.

What might the implementation of a simple decorator look like? In basic terms, a decorator is a callable that takes a callable as input and returns another callable.

装饰器是一种调用函数,将一个调用函数作为输入,返回其他的调用函数。

The following function has that property and could be considered the simplest decorator you could possibly write:

def null_decorator(func):
  return func

As you can see, null_decorator is a callable (it’s a function), it takes another callable as its input, and it returns the same input callable without modifying it.

Let’s use it to decorate (or wrap) another function:

def greet():
  return 'Hello!'

greet = null_decorator(greet)

>>> greet()
'Hello!'

In this example, I’ve defined a greet function and then immediately decorated it by running it through the null_decorator function. I know this doesn’t look very useful yet. I mean, we specifically designed the null decorator to be useless, right? But in a moment this example will clarify how Python’s special-case decorator syntax works.

其实就是包裹既有函数,其方式为将既有函数当做输入参数输入到包装函数中。

Instead of explicitly calling null_decorator on greet and then reassigning the greet variable, you can use Python’s @ syntax for decorating a function more conveniently:

@null_decorator
def greet():
  return 'Hello!'

>>> greet()
'Hello!'

从这里也就可以看出来了,包装器的@的写法为什么可以传递参数在后面,因为本身他就是一种函数,作为对象,自然可以以圆括号调用函数的形式进行执行和参数的传递。

Putting an @null_decorator line in front of the function definition is
the same as defining the function first and then running through the
decorator. Using the @ syntax is just syntactic sugar and a shortcut
for this commonly used pattern.

使用@语法是一种语法糖,且是一般常用的模型。

Note that using the @ syntax decorates the function immediately at definition time. This makes it difficult to access the undecorated original without brittle hacks. Therefore you might choose to decorate some functions manually in order to retain the ability to call the undecorated function as well.

防黑。

Decorators Can Modify Behavior

Now that you’re a little more familiar with the decorator syntax, let’s write another decorator that actually does something and modifies the behavior of the decorated function.

试试别的花样。

Here’s a slightly more complex decorator which converts the result of
the decorated function to uppercase letters:

def uppercase(func):
  def wrapper():
    original_result = func()
    modified_result = original_result.upper()
    return modified_result
  return wrapper

Instead of simply returning the input function like the null decorator did, this uppercase decorator defines a new function on the fly (a closure) and uses it to wrap the input function in order to modify its behavior at call time.

其实这个过程就是upppercase装饰器,运行第一行,将func传入到父函数uppercase中,因为闭包,所以子函数可以获得func,def并不运行,这里到达了 return这一行,然后在return中触发了warpper函数,现在程序开始调用warpper函数,虽然warpper函数圆括号内为空,但是因为闭包的原因,依然得到了func的值,我们将func()返回的结果赋值给了original_result,在这个过程中,输入的func触发了。下面就是将original_result(应该是字符串)经过upper()后赋值给modified_result,最后返回这个结果,至此warpper函数也就运行完毕了。在父函数的__exit__这个内置的命令完成后,整个装饰器下的函数流也就结束了。

The wrapper closure has access to the undecorated input function and
it is free to execute additional code before and after calling the input
function. (Technically, it doesn’t even need to call the input function
at all.)

Note how, up until now, the decorated function has never been executed. Actually calling the input function at this point wouldn’t make any sense—you’ll want the decorator to be able to modify the behavior of its input function when it eventually gets called.

You might want to let that sink in for a minute or two. I know how
complicated this stuff can seem, but we’ll get it sorted out together, I
promise.

花点时间梳理一下。

Time to see the uppercase decorator in action. What happens if you
decorate the original greet function with it?

@uppercase
def greet():
  return 'Hello!'

>>> greet()
'HELLO!'

I hope this was the result you expected. Let’s take a closer look at what
just happened here. Unlike null_decorator, our uppercase decorator
returns a different function object when it decorates a function:

其实之前null_docorator返回的就是输入函数本身。

>>> greet

>>> null_decorator(greet)

>>> uppercase(greet)
.wrapper at 0x76da02f28>

根据地址符号我们可以看出来,null_decorator装饰器反悔的就是func本身,并没有对输入函数进行修改最后将一个被修改的函数返回出去。而uppercase装饰器就是这么干的。

And as you saw earlier, it needs to do that in order to modify the behavior of the decorated function when it finally gets called. The uppercase decorator is a function itself. And the only way to influence the “future behavior” of an input function it decorates is to replace (or wrap) the input function with a closure.

换输入函数来影响未来的行为(就是装饰器下的函数的行为)。

That’s why uppercase defines and returns another function (the closure) that can then be called at a later time, run the original input function, and modify its result.

Decorators modify the behavior of a callable through a wrapper closure so you don’t have to permanently modify the original. The original callable isn’t permanently modified—its behavior changes only when decorated.

装饰器通过封装修正调用性的行为,所以你不需要永久性的修改原函数。原函数的行为是指在被装饰的时候才被改变。

This let’s you tack on reusable building blocks, like logging and other instrumentation, to existing functions and classes. It makes decorators such a powerful feature in Python that it’s frequently used in the standard library and in third-party packages.

反正就是装饰器很牛X就对了。

A Quick Intermission

By the way, if you feel like you need a quick coffee break or a walk around the block at this point—that’s totally normal. In my opinion closures and decorators are some of the most difficult concepts to understand in Python.

闭包和装饰器是属于python最难理解的的概念了。

Please, take your time and don’t worry about figuring this out immediately. Playing through the code examples in an interpreter session one by one often helps make things sink in.

可以找个例子试一下。

I know you can do it!

Applying Multiple Decorators to a Function

Perhaps not surprisingly, you can apply more than one decorator to a function. This accumulates their effects and it’s what makes decorators so helpful as reusable building blocks.

你可以向一个函数应用多个装饰器。

Here’s an example. The following two decorators wrap the output string of the decorated function in HTML tags. By looking at how the tags are nested, you can see which order Python uses to apply multiple decorators:

def strong(func):
  def wrapper():
    return '' + func() + ''
  return wrapper

def emphasis(func):
  def wrapper():
    return '' + func() + ''
  return wrapper

Now let’s take these two decorators and apply them to our greet function at the same time. You can use the regular @ syntax for that and just “stack” multiple decorators on top of a single function:

@strong
@emphasis
def greet():
  return 'Hello!'

上面的例子就是将两个装饰器应用到了一个函数上,先按就是两种效果的叠加。也就是greet返回的字符串的加粗和斜体的合并方式。

What output do you expect to see if you run the decorated function? Will the @emphasis decorator add its tag first, or does @strong have precedence? Here’s what happens when you call the decorated function:

>>> greet()
'Hello!'

This clearly shows in what order the decorators were applied: from bottom to top. First, the input function was wrapped by the @emphasis decorator, and then the resulting (decorated) function got wrapped again by the @strong decorator.

上面清晰地显示了装饰器应用的顺序,从下面到上面。开始输入函数应用的是@emphasis的装饰器,然后再是@strong的装饰器。

To help me remember this bottom to top order, I like to call this behavior decorator stacking. You start building the stack at the bottom and then keep adding new blocks on top to work your way upwards.

行为装饰器堆 先进先出 自下向上的方式

If you break down the above example and avoid the @ syntax to apply the decorators, the chain of decorator function calls looks like this:

decorated_greet = strong(emphasis(greet))

装饰器写法可以转换成上面的写法。

Again, you can see that the emphasis decorator is applied first and then the resulting wrapped function is wrapped again by the strong decorator.

This also means that deep levels of decorator stacking will evenutally have an effect on performance because they keep adding nested function calls. In practice, this usually won’t be a problem, but it’s something to keep in mind if you’re working on performanceintensive code that frequently uses decoration.

装饰器堆的写法会对性能产生影响。

Decorating Functions That Accept Arguments

All examples so far only decorated a simple nullary greet function that didn’t take any arguments whatsoever. Up until now, the decorators you saw here didn’t have to deal with forwarding arguments to the input function.

上面的例子还不需要处理进一步的语句。

If you try to apply one of these decorators to a function that takes arguments, it will not work correctly. How do you decorate a function that takes arbitrary arguments?

This is where Python’s *args and **kwargs feature3 for dealing with variable numbers of arguments comes in handy. The following proxy decorator takes advantage of that:

def proxy(func):
  def wrapper(*args, **kwargs):
    return func(*args, **kwargs)
  return wrapper

利用*args, **kwargs处理输入的参数。

There are two notable things going on with this decorator:

  • It uses the * and ** operators in the wrapper closure definition to collect all positional and keyword arguments and stores them in variables (args and kwargs).
  • The wrapper closure then forwards the collected arguments to the original input function using the * and ** “argument unpacking” operators.

1.在闭包中使用*和**运算符去采集位置参数和关键字参数,并且把他们存储在变量中。
2.闭包封装然后会将采集到的参数传递到原始的输入函数里面,并且对*和**使用参数解构运算。

It’s a bit unfortunate that the meaning of the star and double-star operators is overloaded and changes depending on the context they’re used in, but I hope you get the idea.

Let’s expand the technique laid out by the proxy decorator into a more useful practical example. Here’s a trace decorator that logs function arguments and results during execution time:

def trace(func):
  def wrapper(*args, **kwargs):
    print(f'TRACE: calling {func.__name__}() '
      f'with {args}, {kwargs}')
    original_result = func(*args, **kwargs)

    print(f'TRACE: {func.__name__}() '
      f'returned {original_result!r}')
    return original_result
  return wrapper

上面应用了对函数参数的追踪日志和结果的装饰器。

Decorating a function with trace and then calling it will print the arguments passed to the decorated function and its return value. This is still somewhat of a “toy” example but in a pinch it makes a great debugging aid:

@trace
def say(name, line):
  return f'{name}: {line}'

>>> say('Jane', 'Hello, World')
'TRACE: calling say() with ("Jane", "Hello, World"), {}'
'TRACE: say() returned "Jane: Hello, World"'
'Jane: Hello, World'

Speaking of debugging, there are some things you should keep in mind when debugging decorators:

How to Write “Debuggable” Decorators

When you use a decorator, really what you’re doing is replacing one function with another. One downside of this process is that it “hides” some of the metadata attached to the original (undecorated) function.

当你使用装饰器的时候,实际上你在做的就是用一个函数替换成另外一个函数。这样做的一个负作用就是它会影响一些关联到原函数的元数据。

For example, the original function name, its docstring, and parameter list are hidden by the wrapper closure:

def greet():
  """Return a friendly greeting."""
  return 'Hello!'

decorated_greet = uppercase(greet)

举个栗子,原函数的名字,它的文档字符串,和参数列表被闭包函数给影藏了起来。

If you try to access any of that function metadata, you’ll see the wrapper closure’s metadata instead:

>>> greet.__name__
'greet'
>>> greet.__doc__
'Return a friendly greeting.'

>>> decorated_greet.__name__
'wrapper'
>>> decorated_greet.__doc__
None

闭包函数的元数据和原函数的元数据就大不一样了。

This makes debugging and working with the Python interpreter awkward and challenging. Thankfully there’s a quick fix for this: the functools.wraps decorator included in Python’s standard library.

这是的改bug和使用python的解释器有些迟钝和难度,幸好这里有一个标准库的函数工具 warps装饰器。

You can use functools.wraps in your own decorators to copy over the lost metadata from the undecorated function to the decorator closure. Here’s an example:

import functools

def uppercase(func):
  @functools.wraps(func)
  def wrapper():
    return func().upper()
  return wrapper

我们可以用函数工具functools.wraps在我们的装饰器中复制被装饰的函数的一些数据。

Applying functools.wraps to the wrapper closure returned by the decorator carries over the docstring and other metadata of the input function:

@uppercase
def greet():
  """Return a friendly greeting."""
  return 'Hello!'

>>> greet.__name__
'greet'
>>> greet.__doc__
'Return a friendly greeting.'

As a best practice, I’d recommend that you use functools.wraps in all of the decorators you write yourself. It doesn’t take much time and it will save you (and others) debugging headaches down the road.

建议试一试。

Oh, and congratulations—you’ve made it all the way to the end of this complicated chapter and learned a whole lot about decorators in Python. Great job!

Key Takeaways
  • Decorators define reusable building blocks you can apply to a callable to modify its behavior without permanently modifying the callable itself.
  • The @ syntax is just a shorthand for calling the decorator on an input function. Multiple decorators on a single function are applied bottom to top (decorator stacking).
  • As a debugging best practice, use the functools.wraps helper in your own decorators to carry over metadata from the undecorated callable to the decorated one.
  • Just like any other tool in the software development toolbox, decorators are not a cure-all and they should not be overused. It’s important to balance the need to “get stuff done” with the goal of “not getting tangled up in a horrible, unmaintainable mess of a code base.”

你可能感兴趣的:(Python Tricks - Effective Functions(2))