5个理由告诉你为什么要学习使用Python装饰器,python装饰,未经许可,禁止转载!英文


本文由 编橙之家 - yhx 翻译,艾凌风 校稿。未经许可,禁止转载!
英文出处:Aaron Maxwell。欢迎加入翻译组。

Python 装饰器使用非常地简单。任何会使用 Python 函数的人都可以学习使用装饰器:

Python
@somedecorator
def some_function():
    print("Check it out, I'm using decorators!")

但是如何写装饰器却是另外一回事,而且这并不容易。你必须明白如下这些内容:

  • 闭包
  • 如何将函数做为一阶参数
  • 变量参数
  • 参数解包
  • 甚至是一些 Python 加载源代码的细节

这些知识都需要花费大量的时间去理解掌握。如果你已经累积了许多其它需要学习的内容。这些内容还值得你去学习吗?

对于我来说,答案是肯定的。希望你的答案也是肯定的。那么,自己写装饰器的好处是什么呢?或者说,它们会让我在日常开发过程中哪些事变得容易?

分析、日志与手段

对于大型应用, 我们常常需要记录应用的状态,以及测量不同活动的数量。通过将这些特别的事件包装到函数或方法中,装饰器可以很轻松地满足这些需求,同时保证代码的可读性。

Python
from myapp.log import logger

def log_order_event(func):
    def wrapper(*args, **kwargs):
        logger.info("Ordering: %s", func.__name__)
        order = func(*args, **kwargs)
        logger.debug("Order result: %s", order.result)
        return order
    return wrapper

@log_order_event
def order_pizza(*toppings):
    # let's get some pizza!

这个方法也可以用来记数或者记录其它某些指标。

验证以及运行时检查

Python 是一种强类型语言,但是变量的类型却是动态变化的。虽然这会带来很多好处,但是同时这也意味着更容易引入 bug。对于静态语言,例如 Java, 这些 bug 在编译阶段就可以被发现。因而,你可能希望在对传入或返回的数据进行一些自定义的的检查。装饰器就可以让你非常容易地实现这个需求,并一次性将其应用到多个函数上。

想像一下:你有许多函数,每个函数返回一个字典类型,该字典包含一个“summary ”域。这个域的值不能超过 80 个字符的长度。如果违反这个要求,那就是一个错误。下面这个装饰器会在错误发生时抛出 ValueError 异常:

Python
def validate_summary(func):
    def wrapper(*args, **kwargs):
        data = func(*args, **kwargs)
        if len(data["summary"]) > 80:
            raise ValueError("Summary too long")
        return data
    return wrapper

@validate_summary
def fetch_customer_data():
    # ...

@validate_summary
def query_orders(criteria):
    # ...

@validate_summary
def create_invoice(params):
    # ...

创建框架

一旦你掌握了如何写装饰器,你就能够从其使用的简单的语法中获益颇丰,你可以为语言添加新的语义使其使用更加简单。接下来最棒的就是你可以自己扩展 Python 语法。

事实上,很多开源框架都是使用的这样的方式。 Web 应用框架 Flask 就是使用装饰器将不同 URL 路由到不同处理 HTTP 请求函数的:

Python
# For a RESTful todo-list API.
@app.route("/tasks/", methods=["GET"])
def get_all_tasks():
    tasks = app.store.get_all_tasks()
    return make_response(json.dumps(tasks), 200)

@app.route("/tasks/", methods=["POST"])
def create_task():
    payload = request.get_json(force=True)
    task_id = app.store.create_task(
        summary = payload["summary"],
        description = payload["description"],
    )
    task_info = {"id": task_id}
    return make_response(json.dumps(task_info), 201)

@app.route("/tasks/<int:task_id>/")
def task_details(task_id):
    task_info = app.store.task_details(task_id)
    if task_info is None:
        return make_response("", 404)
    return json.dumps(task_info)

这里有一个全局对象 app,此对象有一个 route 方法。此 route 函数返回一个用于修饰请求处理函数的装饰器。这背后的处理是非常复杂的,但是对于使用 Flask 的程序员来说,所有复杂的东西都被隐藏起来了。

在平时使用 Python 过程中,我们也会这样使用装饰器。例如,所有的对象都依赖于类方法与属性装饰器:

Python
class WeatherSimulation:
    def __init__(self, **params):
         self.params = params

    @classmethod
    def for_winter(cls, **other_params):
        params = {'month': 'Jan', 'temp': '0'}
        params.update(other_params)
        return cls(**params)

    @property
    def progress(self):
        return self.completed_iterations() / self.total_iterations()

这个类有三个不同的 def 语句,但是每一个的语义都是不同的:

  • 构造器是一个简单的方法
  • for_winter 是一个类方法
  • progress 是一个只读的动态属性

@classmethod 装饰器与  @property 装饰器可以让我们在平时使用过程中非常方便地扩展 Python 对象的语义。

复用不能复用的代码

Python 提供了非常强大的工具以将代码包装成易复用的形式,这些工具包括:函数、函数式编程的支持以及一切皆对象的思想。然而,还是存在某些代码并不能通过使用这些工具进行复用。

假设有一个古怪的 API。你可以通过 HTTP 发送 JSON 格式的请求,它 99.9% 的情况下都是正确工作的。但是,小部分请求会返回服务器内部错误的结果。这时候,你需要重新发送请求。在这种情况下,你需要实现重试逻辑,像这样:

Python
resp = None
while True:
    resp = make_api_call()
    if resp.status_code == 500 and tries < MAX_TRIES:
        tries += 1
        continue
    break
process_response(resp)

现在假设你的代码库中有很都地方都进行调用了函数  make_api_call,那么是不是需要在每个调用的地方都实现这个 loop 循环呢?是不是每次添加一次调用都要实现一遍这个循环呢?这种模式能难有一个样板代码,除非你使用装饰器,那么这就变得非常简单了:

Python
# The decorated function returns a Response object,
# which has a status_code attribute. 200 means
# success; 500 indicates a server-side error.

def retry(func):
    def retried_func(*args, **kwargs):
        MAX_TRIES = 3
        tries = 0
        while True:
            resp = func(*args, **kwargs)
            if resp.status_code == 500 and tries < MAX_TRIES:
                tries += 1
                continue
            break
        return resp
    return retried_func

This gives you an easy-to-use @retry decorator:

@retry
def make_api_call():
    # ....

让你的事业腾飞

刚开始写装饰器时可能不是那么容易。虽然这并不像造火箭那么难,但你也需要花费一些时间去学习,掌握其中的奥秘。大部分程序都能够掌握。当你成为团队里面能把装饰器写得很好并且能解决真正的问题的人时,此时其它开发者都会使用你开发的这些装饰器。因为一旦最难的部分,也就是实现装饰器完成后,使用装饰器是非常容易的。这可以极大的放大你所写代码的正面影响,这会让你成为团队的英雄。

我已经培训了成百上千的软件工程师让他们更高效地使用 Python,这些团队一致的反映装饰器是这其中最有价值也是他们在 Python 高阶编程中使用的最重要的工具。这也是为什么其成为了接下来的 Python 课程重点:在 2016 年 5月 25日与 26 日基础在线课程之后。

不管你是以何种方式学习实现装饰器,你都会因其能完成的工作而感到兴奋。毫无疑问,它能永久地改变你使用 Python 的方式 。

评论关闭