[TOC]
Python高级编程 第一部分 函数
第一章 装饰器
装饰器 是一个用于封装函数或类代码的工具. 它显式地将封装器应用到函数或类上, 从而使它们选择加入到装饰器的功能中.
本质上说, 装饰器是一个接受可调用函数的可调用函数, 并返回一个可调用函数.
1.1 用处
函数运行后的清理工作(例如: 输出清理或异常处理)
对于处理已经被装饰的函数或类本身, 装饰器也很有用(例如: 装饰器可以将函数注册到信号系统, 或者注册到Web应用程序的URI注册表中)
1.2 理解装饰器
究其根本, 装饰器就是一个可以接受调用也可以返回调用的调用. 装饰器无非就是一个函数(或调用, 如_call_method_
方法的对象), 该函数接受被装饰的函数作为其位置参数. 装饰器通过使用该参数来执行某些操作, 然后返回原始参数或一些其他的调用(大概以这种方式与装饰器交互).
由于函数在Python中是一级对象, 因此他们能够像其他对象一样被传递到另一个函数. 装饰器就是接受另一个函数作为参数, 应用其完成一些操作的函数.
1.2.1 示例
定义
复制 # 为被装饰的调用点字符串附加了一个字符串
# func.__doc__ 指的是函数func的docstring, 即函数注释(也并非全部)
def decorated_by(func):
func.__doc__ += '\nDecorated by decorated_by'
return func
复制 # 另外一个函数
# 函数的 docstring 是在第一行指定的字符串
def add(x, y):
"""返回 x 和 y 的和"""
return x + y
应用装饰器
复制 add = decorated_by(add)
查看效果
复制 # 源文件: test.py
# codding: utf-8
def decorated_by(func):
func.__doc__ += "\nDecorated by decorated_by"
return func
pass
def add(x, y):
"""返回x和y的和"""
pass
add = decorated_by(add)
help(add)
运行输出
复制 Help on function add in module __main__:
add(x, y)
返回x和y的和
Decorated by decorated_by
装饰器 decorated_by
修改了函数 add(x, y)
的 __doc__
属性, 然后返回原来的函数对象.
1.3 装饰器语法
Python2.5
为装饰器引入了特殊的语法, 在被修饰函数(不包含隐式装饰器的方法签名)声明前加上@decorate_function_name
来使用相应装饰器, 示例如下.
复制 @decorated_by
def add(x, y):
"""返回x和y的和"""
return x + y
1.3.1 装饰器应用顺序
使用装饰器语法(@
)时, 在创建被装饰的可调用函数后, 会立刻应用装饰器, 示例如下.
复制 # python源代码
# codding: utf-8
def decoratedby(func): func._doc += "\nDecorated by decorated_by" return func pass
@decorated_by def add(x, y): """返回x和y的和""" pass
help(add)
复制 **输出**
```python
Help on function add in module __main__:
add(x, y)
返回x和y的和
Decorated by decorated_by
使用多个装饰器
通过@
语法使用多个装饰器, 应用顺序是自底而上, 示例如下.
def decoratedby(func): func._doc += "\nDecorated by decorated_by" return func pass
def alsodecoratedby(func): func.__doc += "\nDecorated by also_decorated_by" return func
@also_decorated_by @decorated_by def add(x, y): """返回x和y的和""" pass
help(add)
复制 **输出**
```python
Help on function add in module __main__:
add(x, y)
返回x和y的和
Decorated by decorated_by
Decorated by also_decorated_by
当装饰器应用到装饰函数时(而不是调用装饰器时), 会执行装饰代码本身.
1.4 装饰器示例
1.4.1 函数注册表
示例一
复制 # codding: utf-8
registry = []
def register(decorated):
registry.append(decorated)
return decorated
@register
def foo():
return 3
@register
def bar():
return 5
answers = []
for func in registry:
answers.append(func())
print(answers)
输出
示例二
复制 # codding: utf-8
class Registry(object):
def __init__(self):
self._functions = []
pass
def register(self, decorated):
self._functions.append(decorated)
return decorated
def run_all(self, *args, **kwargs):
return_values = []
for func in self._functions:
return_values.append(func(*args, **kwargs))
pass
return return_values
pass
a = Registry()
b = Registry()
@a.register
def foo(x=3):
return x
@b.register
def bar(x=5):
return x
@a.register
@b.register
def baz(x=7):
return x
print(a.run_all())
print(b.run_all())
示例二输出
1.4.2 执行时封装代码
1.4.2.1 一个简单的类型检查示例
下面是 一个简单的装饰器, 确保函数接受的所有参数都是整型, 否则报错.
代码
复制 # codding: utf-8
def requires_ints(decorated):
def inner(*args, **kwargs):
kwarg_values = [i for i in kwargs.values()]
for arg in list(args) + kwarg_values:
if not isinstance(arg, int):
raise TypeError('%s only accepts integers as arguments.' % decorated)
return decorated(*args, **kwargs)
return inner
pass
@requires_ints
def add(x, y):
"""返回x和y的和"""
return x + y
help(add)
print(add(3, 5))
# print(add(3, "5")) 会报错
输出
复制 Help on function inner in module __main__:
inner(*args, **kwargs)
8
说明
装饰器自身是require_ints
, 它接受一个参数decorated
, 即被装饰的函数, 装饰器唯一做的事情是返回一个新的可调用函数, 即本地函数inner
, 该函数替代了被装饰的方法.
1.4.2 functools.wraps
简单示例
代码
复制 # codding: utf-8
import functools
def requires_ints(decorated):
@functools.wraps(decorated) # 相比上例, 增加了这一行代码
def inner(*args, **kwargs):
kwarg_values = [i for i in kwargs.values()]
for arg in list(args) + kwarg_values:
if not isinstance(arg, int):
raise TypeError('%s only accepts integers as arguments.' % decorated)
return decorated(*args, **kwargs)
return inner
pass
@requires_ints
def add(x, y):
"""返回x和y的和"""
return x + y
help(add)
print(add(3, 5))
输出
注意和上例输出对比.
复制 Help on function add in module __main__:
add(x, y)
返回x和y的和
8
1.4.3 验证用户
用户验证是装饰器常见用例(在运行被装饰方法之前执行某种正确性检查).
代码
复制 # codding: utf-8
import functools
class User(object):
"""用户类"""
def __init__(self, username, email):
self.username = username
self.email = email
pass
pass
class AnonymousUser(User):
"""匿名用户类"""
def __init__(self):
super().__init__(None, None)
def __bool__(self):
return False
pass
def requires_user(func):
@functools.wraps(func)
def inner(user, *args, **kwargs):
"""验证是否是真实用户"""
if user and isinstance(user, User):
return func(user, *args, **kwargs)
else:
raise ValueError('不是真实用户')
pass
return inner
@requires_user
def test_func(user):
print('username:' + user.username)
print('email:' + user.email)
pass
test_func(User('Hello', 'hello@hello.com'))
try:
test_func('string')
pass
except Exception as e:
print("Exception:", e)
pass
try:
test_func(AnonymousUser())
pass
except Exception as e:
print("Exception:", e)
pass
print(isinstance(AnonymousUser(), User))
print(isinstance(User("", ""), AnonymousUser))
输出
复制 username:Hello
email:hello@hello.com
Exception: 不是真实用户
Exception: 不是真实用户
True
False
注
复制 def __bool__(self):
return False
# 上述类成员方法, 用于在判断类对象真假时, 返回True/False
1.4.4 格式化输出
示例一: 格式化输出为JSON格式
代码
复制 # codding: utf-8
import functools
import json
def json_output(decorate):
@functools.wraps(decorate)
def inner(*args, **kwargs):
result = decorate(*args, **kwargs)
return json.dumps(result)
pass
return inner
pass
@json_output
def test_json_output():
"""返回JSON格式数据"""
d = {'key1': 'value1', 'key2': [1, 2, 3], 'key3': 4}
return d
pass
res = test_json_output()
print(type(res))
print(res)
输出
复制 <class 'str'>
{"key1": "value1", "key2": [1, 2, 3], "key3": 4}
示例二: 格式化输出为JSON格式并且捕获异常并以输出指定的JSON格式数据
代码
复制 # coding: utf-8
import functools
import json
class JSONOutputError(Exception):
def __init__(self, message):
self._message = message
pass
def __str__(self):
return self._message
def json_output(decorate):
@functools.wraps(decorate)
def inner(*args, **kwargs):
try:
result = decorate(*args, **kwargs)
pass
except JSONOutputError as ex:
result = {
'status': 'error',
'message': str(ex)
}
return json.dumps(result)
pass
return inner
pass
@json_output
def test_error():
raise JSONOutputError("throw JSONOutputError exception.")
pass
print(test_error())
输出
复制 {"status": "error", "message": "throw JSONOutputError exception."}
1.4.5 日志管理
代码
复制 # coding: utf-8
import functools
import sys
import time
import logging
print(sys.version)
def logged(method):
@functools.wraps(method)
def inner(*args, **kwargs):
start = time.time()
return_value = method(*args, **kwargs)
end = time.time()
delta = end - start
logger = logging.getLogger('decorator.logged')
logger.warning('Logging:\nCalled method %s at %.2f\nExecution time %.2f seconds\nResult %r.' % (method.__name__, start, delta, return_value))
return return_value
return inner
@logged
def sleep_and_return(return_value):
time.sleep(2)
return return_value
print('Return result:' + sleep_and_return('ABC'))
输出
复制 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)]
Return result:ABC
Logging:
Called method sleep_and_return at 1488270496.60
Execution time 2.00 seconds
Result 'ABC'.
注
1.5 装饰器参数
一个有参数的装饰器代码.
代码
复制 # coding: utf-8
import functools
import json
class JSONOutputError(Exception):
def __init__(self, message):
self._message = message
def __str__(self):
return self._message
def json_output(indent=None, sort_keys=False):
def actual_decorator(decorated):
@functools.wraps(decorated)
def inner(*args, **kwargs):
try:
result = decorated(*args, **kwargs)
pass
except JSONOutputError as e:
result = {
'status': 'error',
'message': str(e)
}
pass
return json.dumps(result, indent=indent, sort_keys=sort_keys)
return inner
return actual_decorator
@json_output(indent=4)
def test_json_output():
return {'key1': 'value1', 'key2': [1, 2, 3], 'key3': 4}
def test_json_output_without_decorate():
return {'key1': 'value1', 'key2': [1, 2, 3], 'key3': 4}
print(test_json_output())
print(test_json_output_without_decorate())
# 这是一个接受单独可调用函数(decorated)作为参数并返回一个可调用函数(inner)的可调用函数.
输出
复制 {
"key1": "value1",
"key2": [
1,
2,
3
],
"key3": 4
}
{'key1': 'value1', 'key2': [1, 2, 3], 'key3': 4}
注
装饰器参数和函数参数: 传递给装饰器的参数只在函数声明并被装饰时处理一次, 传递给函数的参数在该函数被调用时处理.
在封装装饰器代码的示例中, 这些封装的装饰器在局部作用域声明一个内部方法后返回. 该内部方法是实际的装饰器.
接受参数的装饰器额外增加了一层封装, 该接受参数的装饰器并不是实际的装饰器, 而是一个返回装饰器的函数 .
函数能被当作装饰器使用的原因
函数调用的结果被应用到装饰器上.
首先, 解析对json_output
函数的调用
复制 @json_output(indent=4)
def test_json_output():
return {'key1': 'value1', 'key2': [1, 2, 3], 'key3': 4}
然后, Python解析器对函数调用json_output(indent=4)
进行解析, 结果是返回actual_decorator
, 然后返回值被应用@
:
复制 @actual_decorator
def test_json_output():
return {'key1': 'value1', 'key2': [1, 2, 3], 'key3': 4}
这样, 就应用了真正的装饰器actual_decorator
装饰器函数和装饰器的区别
复制 # 代码片段一
@json_output
def test_json_output():
return {'key1': 'value1', 'key2': [1, 2, 3], 'key3': 4}
# 代码片段二
@json_output(indent=4)
def test_json_output():
return {'key1': 'value1', 'key2': [1, 2, 3], 'key3': 4}
# 代码片段一 中, @json_output代码直接对函数使用装饰器json_output进行装饰
# 代码片段二 中, @json_output(indent=4)代码, 首先调用json_output()函数, 如果返回结果是装饰器则对
# test_json_output 进行装饰
一个灵活的装饰器函数
复制 # coding: utf-8
import functools
import json
class JSONOutputError(Exception):
def __init__(self, message):
self._message = message
def __str__(self):
return self._message
def json_output(decorated_=None, indent=None, sort_keys=False):
if decorated_ and (indent or sort_keys):
raise RuntimeError('Unexpected arguments.')
def actual_decorator(decorated):
@functools.wraps(decorated)
def inner(*args, **kwargs):
try:
result = decorated(*args, **kwargs)
pass
except JSONOutputError as e:
result = {
'status': 'error',
'message': str(e)
}
pass
return json.dumps(result, indent=indent, sort_keys=sort_keys)
return inner
if decorated_:
return actual_decorator(decorated_)
else:
return actual_decorator
@json_output
def test_json_output_first():
return {'key1': 'value1', 'key2': [1, 2, 3], 'key3': 4}
@json_output()
def test_json_output_second():
return {'key1': 'value1', 'key2': [1, 2, 3], 'key3': 4}
@json_output(indent=4)
def test_json_output_third():
return {'key1': 'value1', 'key2': [1, 2, 3], 'key3': 4}
def test_json_output_without_decorate():
return {'key1': 'value1', 'key2': [1, 2, 3], 'key3': 4}
print(test_json_output_first())
print(test_json_output_second())
print(test_json_output_third())
print(test_json_output_without_decorate())
注
装饰器函数json_output
支持下面的装饰调用
复制 @json_output
@json_output()
@json_output(indent=5, sort_keys=True)
如果设置了decorated_
, 将作为一个没有方法签名的纯装饰器调用, 应用最终的装饰器并返回inner
函数; 首选调用并解析actual_decorator(decorated_)
函数, 然后以inner
作为唯一参数调用该函数的返回结果.
如果没有设置decorated_
, 那么这就是带有参数关键字的调用, 并且函数返回一个实际的装饰器, 该装饰器接受被装饰的方法并返回inner
函数.
1.6 装饰类
用途
修改一个类的API, 从而使被声明的方式与实例被使用的方式不同
代码
复制 # coding: utf-8
import functools
import time
def sortable_by_creation_time(cls):
original_init = cls.__init__
@functools.wraps(original_init)
def new_init(self, *args, **kwargs):
original_init(self, *args, **kwargs)
self._created = time.time()
pass
cls.__init__ = new_init
cls.__lt__ = lambda self, other: self._created < other._created
cls.__gt__ = lambda self, other: self._created < other._created
return cls
@sortable_by_creation_time
class Sortable(object):
def __init__(self, identifier):
self.identifier = identifier
pass
def __repr__(self):
return self.identifier
pass
first = Sortable('first')
time.sleep(0.1)
second = Sortable('second')
time.sleep(0.1)
third = Sortable('third')
sortables = [second, first, third]
print(sorted(sortables))
输出
说明
被该装饰类装饰的类, 可以按照实例创建时间先后排序
然后, 创建一个将会被赋值给__init__
的新方法
1.7 类型转换
示例
代码
复制 # coding: utf-8
class Task(object):
def run(self, *args, **kwargs):
raise NotImplementedError('Subclasses must implment `run`.')
def identity(self):
return 'I am a task.'
def task(decorated):
class TaskSubclass(Task):
def run(self, *args, **kwargs):
return decorated(*args, **kwargs)
pass
return TaskSubclass
@task
def foo():
return 2 + 2
f = foo()
print(f.run())
print(f.identity())
print(foo())
输出
复制 4
I am a task.
<__main__.task.<locals>.TaskSubclass object at 0x00000204BF14C710>
改进
改进代码以便使用foo()
代替foo().run()
来实现想要的效果.
代码
复制 # coding: utf-8
class Task(object):
def __call__(self, *args, **kwargs):
return self.run(*args, **kwargs)
def run(self, *args, **kwargs):
raise NotImplementedError('Subclasses must implment `run`.')
def identity(self):
return 'I am a task.'
def task(decorated):
class TaskSubclass(Task):
def run(self, *args, **kwargs):
return decorated(*args, **kwargs)
pass
return TaskSubclass()
@task
def foo():
return 2 + 2
print(foo.run())
print(foo())
print(foo.identity())
输出
说明
定义__call__
方法, 使得该类的实例可以向函数一样被调用.