🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## Python 装饰器需要了解的知识 * 闭包 * 如何将函数做为一阶参数 * 变量参数 * 参数解包 * Python 加载源代码的细节 ## 分析、日志与手段 对于大型应用, 我们常常需要**记录应用的状态**,以及测量不同活动的数量。通过将这些特别的事件包装到函数或方法中,装饰器可以很轻松地满足这些需求,同时保证代码的可读性。 ``` from myapp.log import logger def log_order_event(func): def wrapper(*args, **kwargs): logger.info("Ordering: ", func.__name__) order = func(*args, **kwargs) logger.debug("Order result: ", 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 异常: ``` 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 请求函数的: ``` # 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 过程中,我们也会这样使用装饰器。例如,所有的对象都依赖于类方法与属性装饰器: ``` 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% 的情况下都是正确工作的。但是,小部分请求会返回服务器内部错误的结果。这时候,你需要重新发送请求。在这种情况下,你需要实现重试逻辑,像这样: ``` 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 循环呢?是不是每次添加一次调用都要实现一遍这个循环呢?这种模式能难有一个样板代码,除非你使用装饰器,那么这就变得非常简单了: ``` 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 @retry def make_api_call(): # .... ``` ## 让你的事业腾飞 刚开始写装饰器时可能不是那么容易。虽然这并不像造火箭那么难,但你也需要花费一些时间去学习,掌握其中的奥秘。大部分程序都能够掌握。当你成为团队里面能把装饰器写得很好并且能解决真正的问题的人时,此时其它开发者都会使用你开发的这些装饰器。因为一旦最难的部分,也就是实现装饰器完成后,使用装饰器是非常容易的。这可以极大的放大你所写代码的正面影响,这会让你成为团队的英雄。