装饰器

[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
  • 使用多个装饰器

    通过@语法使用多个装饰器, 应用顺序是自底而上, 示例如下.

    # codding: utf-8

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)

输出

[3, 5]

示例二

# 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())

示例二输出

[3, 7]
[5, 7]

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))

输出

[first, second, third]

说明

  • 被该装饰类装饰的类, 可以按照实例创建时间先后排序

  • 首先, 保存了累的原始方法__inIT__的副本

  • 然后, 创建一个将会被赋值给__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())

输出

4
4
I am a task.

说明

  • 定义__call__方法, 使得该类的实例可以向函数一样被调用.

最后更新于

这有帮助吗?