《Python高级编程》(一)装饰器

定义

装饰器是一个用于封装函数或类的代码的工具。装饰器就是接受另一个函数作为参数,并用其完成一些操作的函数。

装饰器的本质

  • 本质:装饰器就是一个可以接受调用也可以返回调用的调用
>>> def decorated_by(func):
...     func.__doc__ += '\nDecorated by decorated_by'
...     return func
... 
>>> def add(x, y):
...     """Return the sum of x and y"""
...     return x+y
... 
>>> add = decorated_by(add)
>>> help(add)
  • 通过在装饰器名称前放置@字符实现,即:
    @decorated_by 等同于 add = decorated_by(add)
>>> @decorated_by
... def add1(x,y):
...     """Return the sum of x and y"""
...     return x+y
  • 通过@使用多个装饰器时,需要按照自底向上的顺序应用

装饰器应用示例

  • 类中的@classmethod、@staticmethod (使一个类上的方法不需要这个类的实例)
  • Django中的@login_required、@permission_required
  • Flask中的@app.route

编写装饰器的用例

  • 附加功能:检查身份、记录函数结果等
  • 数据的清理或添加:
  • 函数注册:在任务运行器中注册一个任务或带有信号处理器的函数

案例

1 函数注册表
  • 可以拥有完全分离的注册表
  • 也可以在多个注册表中注册同一个函数
class Registry:
        def __init__(self):
            self.functions = []
        """
        函数注册表:register是一个装饰器,位置参数被装饰到注册表变量,
        并返回未改变的装饰方法。
        任何接收register装饰器的方法都将把自身附加到functions
        """
        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))
            return return_values
# 可以拥有完全分离的注册表
# 也可以在多个注册表中注册同一个函数
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()  #[3, 7]
print b.run_all()  #[5, 7]
print a.run_all(x=4) #[4, 4]    
2 用户验证案例
  • 使用场景:一个期望将用户作为其第一个参数的方法,可以使用如下装饰器做检查
#-*- coding:utf8 -*-

class User(object):
    """A representation of a user in our application"""
    def __init__(self, username, email):
        self.username = username
        self.email = email

class AnonymousUser(User):
    """An Anonymous User; a stand-in for an actual user"""
    def __init__(self):
        User.__init__(self, None, None)

    def __nonzero__(self):
        return False

import functools
# 前面的方法会使得调用时丢失一些重要的功能信息,比如__name__
# functools.wraps python实现的一个装饰器,可以将原函数对象的指定属性复制给包装函数对象, 默认有 __module__、__name__、__doc__,或者通过参数选择。
def requires_user(func):
    @functools.wraps(func)
    def inner(user, *args, **kwargs):
        print "%s was used." % user.__name__
        if user and isinstance(user, User):
            return func(user, *args, **kwargs)
        else:
            raise ValueError('A valid user is required to run this.')
    return inner

a = User('xiaoming', '12345@qq.com')

@requires_user
def test(user):
    print user

test(a)
3 输出格式化

在所有相关函数的结尾手动将结果转换为JSON格式非常繁琐,可使用装饰器实现格式化

import functools
import json

class JSONOutputError(Exception):
    def __init__(self, message):
        self._message = message
    def __str__(self):
        return self._message

def json_output(decorated):
    @functools.wraps(decorated)
    def inner(*args, **kwargs):
        try:
            result = decorated(*args, **kwargs)
        except JSONOutputError as ex:
            result = {
                'status':'error',
                'message':str(ex),
            }
        return json.dumps(result)
    return inner

@json_output
def error():
    raise JSONOutputError('This function is erratic')

print error() # {"status": "error", "message": "This function is erratic"}

@json_output
def do_nothing():
    return {'status':'done'}

print do_nothing()   #{"status": "done"}
4 日志管理
  • 通用的日志管理函数。logged装饰器的功能:调用函数时对函数执行时间进行计时,并将结果记录到日志内
import functools
import logging
import time

def logged(method):
    """Cause the decorated method to be run and its results logged, along with some other diagnostic information"""
    @functools.wraps(method)
    def inner(*args, **kwargs):
        #record start time
        start = time.time()
        #Run the decorated method
        return_value = method(*args, **kwargs)
        #record end time
        end = time.time()
        delta = end - start
        #Log the method
        #logger = logging.getLogger('decorator.logged')
        logging.warning('called method %s at %.02f; execution time %.02f seconds; result %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 sleep_and_return(42) 

执行结果:
42
WARNING:root:called method sleep_and_return at 1546072641.69; execution time 2.00 seconds; result 42. 42

5 带参数的装饰器

在案例3的基础上改进,装饰器得到一个带有缩进和换行的JSON块。

import functools
import json

class JSONOutputError(Exception):
    def __init__(self, message):
        self._message = message
    def __str__(self):
        return self._message

def json_output_args(indent=None, sort_keys=False):
    def actual_decorator(decorated):
        @functools.wraps(decorated)
        def inner(*args, **kwargs):
            try:
                result = decorated(*args, **kwargs)
            except JSONOutputError as ex:
                result = {
                    'status': 'error',
                    'message': str(ex),
                }
            return json.dumps(result, indent=indent, sort_keys=sort_keys)

        return inner

    return actual_decorator


@json_output_args(indent=4)
def do_nothing():
    return {'status': 'done'}


print do_nothing()

运行结果:
{
“status”: “done”
}

6.补充几个号的实例

(https://blog.csdn.net/hesi9555/article/details/70224911)

#-*- coding:utf-8 -*-
def catch_exception(origin_func):
    def wrapper(self, *args, **kwargs):  #装饰器需要调用类里面的另一个方法来处理异常,方法:wrapper增加一个参数:self.
        try:
            u = origin_func(self, *args, **kwargs)
            return u
        except Exception:
            self.revive()
            return 'an Exception raised.'
    return wrapper


class Test(object):
    def __init__(self):
        pass

    def revive(self):
        print('revive from exception.')
        # do something to restore

    @catch_exception
    def read_value(self):
        print('here I will do something.')
        # do something.
        #self.revive()
        raise ValueError


test = Test()
print test.read_value()
#通过添加一个self参数,类外面的装饰器就可以直接使用类里面的各种方法,也可以直接使用类的属性

运行结果:
here I will do something.
revive from exception.
an Exception raised.

装饰类

  • 用途
    • 类装饰器可以与被装饰类的属性交互
    • 类装饰器可以添加属性或将属性参数化,或是修改一个类的API,从而使类被声明的方式与实例被使用的方式不同
  • 案例:
    • 该装饰器首先保存了类的原始方法__init__的副本,再创建一个_init_新方法,完成__init__赋值时间戳的属性
    • 装饰器还加入了__lt__和__gt__魔术方法
import time
import functools

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

    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
    def __repr__(self):
        return self.identifier

first = Sortable('first')
time.sleep(1)
second = Sortable('second')
time.sleep(1)
third = Sortable('third')
sortable = [second, first, third]
print sortable
print sorted(sortable)

Debug _create值
运行结果(多谢weixin_37491207指正):
[second, first, third]
[first, second, third]

类型转换

  • 装饰器的唯一需求是一个可调用函数接受一个可调用函数并返回一个可调用函数,但并没有要求必须返回同种类型的可调用函数
    • 更高级的装饰器是:装饰器装饰一个函数,但返回一个类
    • 案例:装饰器创建了Task的一个子类并返回该类,该类是一个可调用函数并调用一个类创建该类的实例,返回该类的__init__方法
class Task(object):
    """A trivial task class. Task classes have a run method, with run the task"""

    def run(self, *args, **kwargs):
        raise NotImplementedError('Subclasses must implement run')

    def identify(self):
        return 'I am a task'

def task(decorated):
    """Return a class that runs the given function if its run method is called"""
    class TaskSubclass(Task):
        def run(self, *args, **kwargs):
            return decorated(*args, **kwargs)

    return TaskSubclass

@task
def foo():
    return 2 + 2

f = foo()
print f.run()
print f.identify()

运行结果:
4
I am a task

  • 使用一个装饰器可以将一个函数替换为一个类,可以使开发人员只需要考虑所编写任务的实际内容

问题

  • 任务被@task_class装饰器装饰时,它会变为一个类,执行foo()时会出错,即:
>>> print foo()
<__main__.TaskSubclass object at 0x0171BE10>

与实际需求不服(应该返回4)

改进方法

(1)调用基类Task时新增__call__方法。
(2)@task_class装饰器返回TaskSubclass类的实例而不是类本身

- 能够接受该方案是由于对于装饰器来说唯一的要求是返回一个可调用函数,而Task新增的__call__方法意味着它的实例现在可以被调用
- 该模式的价值:Task类虽然简单,但可以看到如何将更多的功能添加进来,这对于管理和执行任务非常有用。

class Task(object):
	"""A trivial task class. Task classes have a run method, with run the task"""
	def __call__(self, *args, **kwargs):
	    return self.run(*args, **kwargs)
	def run(self, *args, **kwargs):
	    raise NotImplementedError('Subclasses must implement run')
	def identify(self):
	    return 'I am a task'

def task(decorated):
    """Return a class that runs the given function if its run method is called"""
    class TaskSubclass(Task):
        def run(self, *args, **kwargs):
            return decorated(*args, **kwargs)
    return TaskSubclass()

@task
def foo():
    return 2+2

print  foo()

结果为4

但是运行f=foo(); f.run()是报错:(待查找问题)

print f.run()
AttributeError: 'int' object has no attribute 'run'

总结

  • 装饰器只是一个函数,具有其他函数的所有灵活性,它可以为了响应输入而完成所需要完成的工作。
  • 本质上,装饰器是一个接受可调用函数的可调用函数,并返回一个可调用函数。即装饰器可以被用于装饰类和函数(类本身也是可调用函数)
  • 考虑使用装饰器作为一种封装不相关函数开头和结尾功能的方法。装饰器是用于函数注册、发信号、某种情况下的类增强以及其他功能的强大工具

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值