## 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():
# ....
```
## 让你的事业腾飞
刚开始写装饰器时可能不是那么容易。虽然这并不像造火箭那么难,但你也需要花费一些时间去学习,掌握其中的奥秘。大部分程序都能够掌握。当你成为团队里面能把装饰器写得很好并且能解决真正的问题的人时,此时其它开发者都会使用你开发的这些装饰器。因为一旦最难的部分,也就是实现装饰器完成后,使用装饰器是非常容易的。这可以极大的放大你所写代码的正面影响,这会让你成为团队的英雄。
- 前言
- 环境搭建
- pypi
- 打包
- Python 2 和 Python 3 的版本之间差别
- 项目
- 第一部分
- 第1章 基础
- Python安装
- python代码文件类型
- python对象
- 核心数据类型
- 核心数据类型--整型和浮点型
- 核心数据类型--字符串
- str.format
- 核心数据类型--列表
- 核心数据类型--元组
- 核心数据类型--字典
- 核心数据类型--集合
- 核心数据类型--文件对象
- 调用bash
- 标准输入输出
- str-repr
- 字符编码
- 迭代器和生成器
- 第2章 语句和语法
- 赋值语句
- if语句
- while语句
- for语句
- assert
- 第3章 函数
- 函数作用域
- 工厂函数
- 内置函数
- 递归
- 嵌套作用域和lambda
- 参数传递
- 函数式编程
- property可写与可读
- 第5章 模块
- 模块导入
- 模块命名空间
- 相对导入和绝对导入
- 模块重载
- 在模块中隐藏数据
- 过渡性重载
- 第6章 类
- 面向对象还是面向过程?
- 构造函数 析构函数
- call
- 运算符重载
- str()
- 待定
- 即时生成属性
- 多态
- 线程和进程
- thread模块
- threading模块
- threading线程锁
- 糖果机
- multiprocessing
- 阻塞非阻塞同步异步
- 单线程和多线程对比
- 生产者消费者模型
- 第二部分
- 获取系统资源信息
- 获取进程所占的物理内存
- dmidecode获取系统信息
- 网络编程
- 网络基础
- python中的套接字
- socket模块
- 第三部分 高级功能
- 闭包入门
- 闭包的应用
- 装饰器入门
- 装饰器应用
- 第四部分 项目实战
- graphite
- 模块
- collections
- datetime
- Enum
- faker
- fabric
- fileinput
- fire
- fnmatch
- getpass
- glob
- hashlib
- heapq
- json模块
- log
- os
- Paramiko
- parser
- platform
- pyyaml
- Queue
- random
- re
- 特殊符号和字符
- re模块
- shelves
- subprocess
- time
- urllib_urllib2_requests
- urllib urllib2
- requests
- 标准模块ConfigParser
- 扩展模块Mysqldb
- 扩展模块dns
- 扩展模块request
- uuid
- cacheout 缓存库
- delorean 时间
- 附录
- 内置函数
- python实现各种排序算法
- 常见报错
- pymongo
- pyrocksdb
- 常用
- ERROR