Python装饰器的高级用法

Python装饰器的高级用法(翻译)

原文地址
https://www.codementor.io/python/tutorial/advanced-use-python-decorators-class-function

介绍

我写这篇文章的主要目的是介绍装饰器的高级用法。如果你对装饰器知之甚少,或者对本文讲到的知识点易混淆。我建议你复习下装饰器基础教程。
本教程的目标是介绍装饰器的一些有趣的用法。特别是怎样在类中使用装饰器,怎样给装饰器传递额外的参数。

装饰器 vs 装饰器模式

Decorator模式是一个面向对象的设计模式,它允许动态地往现有的对象添加行为。当你装饰了一个对象,在某种程度上,你是在独立于同一个类的其他实例的基础上扩展其功能。
Python装饰器不是装饰器模式的实现,它在函数、方法定义的时候添加功能,而不是在运行的时候添加。Decorator设计模式本身可以在Python中实现,因为Python是动态编程语言,所以没有必要这样做。

一个基础的装饰器

这是装饰器的最简单例子,在继续往下面阅读之前请确保理解此段代码。如果你需要更多关于此代码的解释,请复习下基础装饰器教程。

def time_this(original_function): 
    def new_function(*args, **kwargs):
        import datetime 
        before = datetime.datetime.now() 
        x = original_function(*args, **kwargs) 
        after = datetime.datetime.now() 
        print("Elapsed Time = {}".format(after-before)) 
        return x 
    return new_function
@time_this
def func_a(stuff): 
    import time 
    time.sleep(stuff) 
    func_a(3)
# out:
Elapsed Time = 0:00:03.012472

带参数的装饰器

有时候带参数的装饰器会非常有用,这种技术经常用在函数注册中。在web框架Pyramid中经常有用到,例如:

@view_config(route_name='home', renderer='templates/mytemplate.pt')
def my_view(request): 
    return {'project': 'hello decorators'} 

比方说,我们有一个用户可以登录并且可以和用户交互的GUI应用程序。用户和GUI界面的交互触发事件,导致Python函数执行。假设有许多使用该图形界面的用户,他们各自的权限级别差异很大,不同的功能执行需要不同的权限。比如,考虑以下功能:

# 假设这些函数是存在的
def current_user_id(): 
    """ this function returns the current logged in user id, if the use is not authenticated the return None """
def get_permissions(iUserId): 
    """ returns a list of permission strings for the given user. For example ['logged_in','administrator','premium_member'] """
# 在这些函数中我们需要实现权限检查 
def delete_user(iUserId): 
    """ delete the user with the given Id. This function is only accessable to users with administrator permissions """ 
def new_game(): 
    """ any logged in user can start a new game """ 
def premium_checkpoint(): 
    """ save the game progress, only accessable to premium members """

一种实现这些权限检查的方式是实现多个装饰器,比如:

def requires_admin(fn):  
    def ret_fn(*args,**kwargs): 
        lPermissions = get_permissions(current_user_id()) 
        if 'administrator' in lPermissions: 
            return fn(*args,**kwargs) 
        else: raise Exception("Not allowed") 
    return ret_fn
def requires_logged_in(fn): 
    def ret_fn(*args,**kwargs): 
        lPermissions = get_permissions(current_user_id()) 
        if 'logged_in' in lPermissions: 
            return fn(*args,**kwargs) 
        else: 
            raise Exception("Not allowed") 
        return ret_fn 
def requires_premium_member(fn): 
    def ret_fn(*args,**kwargs): 
        lPermissions = get_permissions(current_user_id()) 
        if 'premium_member' in lPermissions: 
            return fn(*args,**kwargs) 
        else: 
            raise Exception("Not allowed") 
        return ret_fn 
@requires_admin
def delete_user(iUserId): 
""" delete the user with the given Id. This function is only accessable to users with administrator permissions """
@requires_logged_in
def new_game(): 
""" any logged in user can start a new game """ @requires_premium_member
def premium_checkpoint(): 
""" save the game progress, only accessable to premium members """

但是,这太可怕了。这需要大量的复制粘贴,每个装饰器需要一个不同的名字,如果有任何关于权限检查的改变,每个装饰器都需要修改。就没有一个装饰器把以上三个装饰器的工作都干了的吗?
为了解决此问题,我们需要一个返回装饰器的函数:

def requires_permission(sPermission):  
    def decorator(fn):  
        def decorated(*args,**kwargs):  
            lPermissions = get_permissions(current_user_id())  
            if sPermission in lPermissions:  
                return fn(*args,**kwargs)
            raise Exception("permission denied")  
        return decorated  
    return decorator
def get_permissions(iUserId):  
    # this is here so that the decorator doesn't throw NameErrors 
    return ['logged_in',]
def current_user_id(): 
    #ditto on the NameErrors 
    return 1
#and now we can decorate stuff... 
@requires_permission('administrator')
def delete_user(iUserId): 
""" delete the user with the given Id. This function is only accessible to users with administrator permissions """
@requires_permission('logged_in')
def new_game(): 
""" any logged in user can start a new game """ @requires_permission('premium_member')
def premium_checkpoint(): 
""" save the game progress, only accessable to premium members """

尝试一下调用delete_usernew namepremium_checkpoint然后看看发生了什么。
premium_checkpointdelete_user 产生了一个“permission denied”的异常,new_game执行正常。
下面是带参数装饰的一般形式,和例子的使用:

def outer_decorator(*outer_args,**outer_kwargs):  
    def decorator(fn):  
        def decorated(*args,**kwargs):
            do_something(*outer_args,**outer_kwargs) 
            return fn(*args,**kwargs)  
        return decorated  
    return decorator  
@outer_decorator(1,2,3)
def foo(a,b,c): 
    print(a) 
    print(b) 
    print(c)
foo()

等价于:

def decorator(fn):  
    def decorated(*args,**kwargs):  
        do_something(1,2,3)  
        return fn(*args,**kwargs)  
    return decorated 
return decorator  
@decorator
def foo(a,b,c): 
    print(a)
    print(b) 
    print(c)
foo()

类装饰器

装饰器不仅可以修饰函数,还可以对类进行装饰。比如说,我们有一个类,该类含有许多重要的方法,我们需要记录每一个方法执行的时间。我们可以使用上述的time_this装饰此类:

class ImportantStuff(object): 
@time_this 
def do_stuff_1(self): 
    pass
@time_this 
def do_stuff_2(self): 
    pass
@time_this 
def do_stuff_3(self): 
    pass

此方法可以运行正常。但是在该类中存在许多多余的代码,如果我们想建立更多的类方法并且遗忘了装饰其中的一个方法,如果我们不想装饰该类中的方法了,会发生什么样的情况呢?这可能会存在出现认为错误的空间,如果写成这样会更有好:

@time_all_class_methods
class ImportantStuff: 
    def do_stuff_1(self):
        pass
    def do_stuff_2(self):
        pass
    def do_stuff_3(self):
        pass

等价于:

class ImportantStuff: 
    def do_stuff_1(self):
        pass
    def do_stuff_2(self):
        pass
    def do_stuff_3(self):
        pass
ImportantStuff = time_all_class_methods(ImportantStuff)

那么time_all_class_methods是怎么工作的呢?
首先,我们需要采用一个类作为参数,然后返回一个类,我们也要知道返回的类的功能应该和原始类ImportantStuff功能一样。也就是说,我们仍然希望做重要的事情,我们希望记录下每个步骤发生的时间。我们写成这样:

def time_this(original_function):  
    print("decorating")  
    def new_function(*args,**kwargs): 
        print("starting timer")  
        import datetime  
        before = datetime.datetime.now()  
        x = original_function(*args,**kwargs)  
        after = datetime.datetime.now()  
        print("Elapsed Time = {0}".format(after-before))  
        return x  
    return new_function
def time_all_class_methods(Cls): 
    class NewCls: 
        def __init__(self,*args,**kwargs): 
            self.oInstance = Cls(*args,**kwargs) 
        def __getattribute__(self,s): 
            try:  
                x = super(NewCls,self).__getattribute__(s) 
            except AttributeError:  
                pass 
            else: 
                return x 
            x = self.oInstance.__getattribute__(s) 
            if type(x) == type(self.__init__): 
                return time_this(x) 
            else: 
                return x 
        return NewCls
@time_all_class_methods
class Foo: 
    def a(self): 
        print("entering a") 
        import time 
        time.sleep(3) 
        print("exiting a")
oF = Foo()
oF.a()
# out:
decorating
starting timer
entering a
exiting a
Elapsed Time = 0:00:03.006767

总结

在此篇教程中,我们给大家展示了一些Python装饰器使用的技巧-我们介绍了怎么样把参数传递给装饰器,怎样装饰类。但是这仅仅是冰山一角。除了本文介绍的之外,还有其他好多装饰器的使用方法,我们甚至可以使用装饰器装饰装饰器(如果你有机会使用到它,这可能是一个做全面检查的好方法)。Python有一些内置的装饰器,比如:staticmethodclassmethod
阅读完本文还需要学习什么呢?通常是没有比我在文章中展示的装饰器更复杂的了,如果你有兴趣学习更多关于改变类功能的方法,我建议您阅读下继承和OOP设计原则。或者你可以试试阅读一下元类。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,890评论 18 139
  • 每个人都有的内裤主要功能是用来遮羞,但是到了冬天它没法为我们防风御寒,咋办?我们想到的一个办法就是把内裤改造一下,...
    chen_000阅读 1,370评论 0 3
  • www.yunxcloud.cn 首先要明白装饰器是用来给函数增加额外功能的。 常用的工具函数 import ti...
    彩色系阅读 1,031评论 0 1
  • 两本不错的书: 《Python参考手册》:对Python各个标准模块,特性介绍的比较详细。 《Python核心编程...
    静熙老师哈哈哈阅读 3,386评论 0 80
  • 她放学回家,打开手机 聊天记录仍然一片空白 看一部回忆的电影—— 在那里,有他作主角; 想像依据回忆在虚无中 莽撞...
    西夏伏阅读 140评论 0 0