您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > Python

Python装饰器以及高级用法

时间:2019-07-01 10:54:49  来源:  作者:
Python装饰器以及高级用法

 

介绍

首先我要承认,装饰器非常难!你在本教程中看到的一些代码将会有一些复杂。大多数人在学习Python时都跟装饰器做过斗争,所以如果这对你来说很奇怪,不要感到沮丧,因为同样的大多数人都可以克服这种苦难。在本教程中,我将逐步介绍了解装饰器的过程。首先我假设你已经可以编写基本函数和基本类。如果你不能做这些事,那么我建议你在回到这里之前先学习如何去做到编写基本函数和基本类(除非你迷路了,在这种情况下你可以原谅)。

用例:计时函数执行

假设我们正在执行一段代码,执行时间比我们想的还要长一些。这段代码由一堆函数调用组成,我们确信这些调用中至少有一个调用构成了我们代码中的瓶颈。我们如何找到瓶颈?现在有一个解决方案,就是我们现在要关注的解决方案,就是对函数执行进行计时。

让我们从一个简单的例子开始。我们只有一个函数需要计时,func_a

def func_a(stuff):
 do_important_things_1()
 do_important_things_2()
 do_important_things_3()

一种方法是将时钟代码放在每个函数调用周围。所以就像这样:

func_a(current_stuff)

看起来会更像这样:

before = datetime.datetime.now()
func_a(current_stuff)
after = datetime.datetime.now()
print ("Elapsed Time = {0}".format(after-before))

这样就可以了。但是如果我们有多次调用func_a并且我们想要为所有这些计时会发生什么呢?我们可以用计时代码包围func_a的每个调用,但是这样做也有不好的效果。它只准备编写一次计时代码。因此,我们将其放在函数定义中,而不是将其放在函数之外。

def func_a(stuff):
 before = datetime.datetime.now()
 do_important_things_1()
 do_important_things_2()
 do_important_things_3()
 after = datetime.datetime.now()
 print("Elapsed Time = {0}".format(after-before))

这种方法的好处是:

  1. 我们将代码放在一个地方,所以如果我们想要更改它(例如,如果我们想将经过的时间存储在数据库或日志中)那么我们只需要在一个地方而不是每一个函数调用中更改它
  2. 我们不需要记住每次调用func_a都要写四行代码而不是一行,这是非常好的

好的,但是只需要计算一个函数的时间是不现实的。如果你需要对一件事进行计时,你很有可能需要至少对两件事进行计时。所以我们会选择三个。

def func_a(stuff):
 before = datetime.datetime.now()
 do_important_things_1()
 do_important_things_2()
 do_important_things_3()
 after = datetime.datetime.now()
 print("Elapsed Time = {0}".format(after-before))
def func_b(stuff):
 before = datetime.datetime.now()
 do_important_things_4()
 do_important_things_5()
 do_important_things_6()
 after = datetime.datetime.now()
 print("Elapsed Time = {0}".format(after-before))
def func_c(stuff):
 before = datetime.datetime.now()
 do_important_things_7()
 do_important_things_8()
 do_important_things_9()
 after = datetime.datetime.now()
 print("Elapsed Time = {0}".format(after-before))

这看起来很糟糕。如果我们想要对8个函数进行计时的时候怎么办?然后我们决定将计时的信息存储在日志文件中。然后我们决定建立一个更好的数据库。我们这里需要的是将一种相同的代码合并到func_a,func_b和func_c中的方法,这种方法不会让我们到处复制粘贴代码。

一个简单的绕道:返回函数的函数

Python是一种非常特殊的语言,因为函数是第一类对象。这意味着一旦函数在作用域中被定义,它就可以传递给函数,赋值给变量,甚至从函数返回。这个简单的事实是使python装饰器成为可能的原因。查看下面的代码,看看你是否可以猜出标记为A,B,C和D的行会发生什么。

def get_function():
 print ("inside get_function") 
 def returned_function(): 
 print("inside returned_function") 
 return 1
 print("outside returned_function")
 return returned_function
returned_function() # A 
x = get_function() # B 
x # C 
x() # D

A

这一行给出了一个NameError并声明returned_function不存在。但我们只是定义了它,对吧?你在这里需要知道的是,它是在get_function的范围内定义的。也就是说,在get_function里面定义了它。它不是在get_function之外。如果这让你感到困惑,那么你可以尝试使用该locals()函数,并阅读Python的范围。

B

这行代码打印出以下内容:

inside get_function
outside returned_function

此时Python不执行returned_function的任何内容。

C

这一行输出:

<function returned_function at 0x7fdc4463f5f0>

也就是说,get_function()返回的值x本身就是一个函数。

尝试再次运行B和C行。请注意,每次重复此过程时,返回的returned_function地址都是不同。每次调用get_function都会生成新的returned function。

d

因为x是函数,所以就可以调用它。调用x就是调用returned_function的一个实例。这里输出的是:

inside returned_function
1

也就是说,它打印字符串,并返回值1。

回到时间问题

你现在仍然在看么?如此我们有了新的知识,那么我们如何解决我们的老问题?我建议我们创建一个函数,让我们调用它并称为time_this,它将接收另一个函数作为参数,并将参数函数封装在某些计时代码中。有点像:

def time_this(original_function): # 1
 def new_function(*args,**kwargs): # 2
 before = datetime.datetime.now() # 3
 x = original_function(*args,**kwargs) # 4
 after = datetime.datetime.now() # 5
 print("Elapsed Time = {0}".format(after-before)) # 6
 return x # 7
 return new_function() # 8

我承认它有点疯狂,所以让我们一行一行的看下去:

1这只是time_this的原型。time_this是一个函数就像任何其他函数一样,并且只有一个参数。 2我们在内部定义一个函数time_this。每当time_this执行时它都会创建一个新函数。 3计时代码,就像之前一样。 4我们调用原始函数并保留结果以供日后使用。 5,6剩余的计时代码。 7new_function必须像原始函数一样运行,因此返回存储的结果。 8返回在time_this中创建的函数。

现在我们要确保我们的函数是计时的:

def func_a(stuff):
 do_important_things_1()
 do_important_things_2()
 do_important_things_3()
func_a = time_this(func_a) # <---------
def func_b(stuff):
 do_important_things_4()
 do_important_things_5()
 do_important_things_6()
func_b = time_this(func_b) # <---------
def func_c(stuff):
 do_important_things_7()
 do_important_things_8()
 do_important_things_9()
func_c = time_this(func_c) # <---------

看看func_a,当我们执行时func_a = time_this(func_a)我们用time_this返回的函数替换func_a。所以我们用一个函数替换func_A该函数执行一些计时操作(上面的第3行),将func a的结果存储在一个名为x的变量中(第4行),执行更多的计时操作(第5行和第6行),然后返回func_a返回的内容。换句话说func_a,仍然以相同的方式调用并返回相同的东西,它也只是被计时了。是不是感觉很整洁?

介绍装饰器

我们所做的工作很好,而且非常棒,但是很难看,非常难读懂。所以Python可爱的作者给了我们一种不同的,更漂亮的写作方式:

@time_this
def func_a(stuff):
 do_important_things_1()
 do_important_things_2()
 do_important_things_3()

完全等同于:

def func_a(stuff):
 do_important_things_1()
 do_important_things_2()
 do_important_things_3()
func_a = time_this(func_a)

这通常被称为语法糖。@没有什么神奇的。这只是一个已达成一致的惯例。沿着这条路上的某个地方决定了。

总结

装饰器只是一个返回函数的函数。如果这些东西看起来非常的 - 那么请确保以下主题对你有意义然后再回到本教程:

  • Python函数
  • 范围
  • Python作为第一类对象(甚至可以查找lambda函数,它可能使它更容易理解)。

另一方面,如果你对更多的话题感兴趣的话,你可能会发现:

  • 如装饰类:

python @add_class_functionality class MyClass: ...

  • 具有更多参数的装饰器, 例如:

python @requires_permission(name="edit") def save_changes(stuff): ...

下面就是我要介绍的高级装饰器的主题。

装饰器的高级用法

介绍

下面这些旨在介绍装饰器的一些更有趣的用法。具体来说,如何在类上使用装饰器,以及如何将额外的参数传递给装饰器函数。

装饰者与装饰者模式

装饰器模式是一种面向对象的设计模式,其允许动态地将行为添加到现有的对象当中。当你装饰对象时,你将以独立于同类的其他实例方式扩展它的功能。

Python装饰器不是装饰器模式的实现。Python装饰器在定义时向函数和方法添加功能,它们不用于在运行时添加功能。装饰器模式本身可以在Python中实现,但由于Python是Duck-teped的,因此这是一件非常简单的事情。

一个基本的装饰

这是装饰器可以做的一个非常基本的例子。我只是把它作为一个参考点。在继续之前,请确保你完全理解这段代码。

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 = {0}".format(after-before)) 
 return x 
 return new_function 
@time_this
def func_a(stuff):
 import time
 time.sleep(3)
func_a(1)

接受参数的装饰器

有时,除了装饰的函数之外,装饰器还可以使用参数。这种技术经常用于函数注册等事情。一个著名的例子是Pyramid Web应用程序框架中的视图配置。例如:

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

假设我们有一个应用程序,用户可以登录并与一个漂亮的gui(图形用户界面)进行交互。用户与gui的交互触发事件,而这些事件导致Python函数被执行。让我们假设有很多用户使用这个应用程序,并且他们有许多不同的权限级别。执行不同的功能需要不同的权限类型。例如,考虑以下功能:

#这些功能是存在的
def current_user_id():
 """
 此函数返回当前登录的用户ID,如果没有经过身份验证,则返回None 
 """
def get_permissions(iUserId):
 """
 返回给定用户的权限字符串列表,例如 ['logged_in','administrator','premium_member']
 """
#我们需要对这些函数进行权限检查
def delete_user(iUserId):
 """
 删除具有给定ID的用户,只有管理员权限才能访问此函数
 """
def new_game():
 """
 任何已登录的用户都可以启动一个新游戏
 """
def premium_checkpoint():
 """
 保存游戏进程,只允许高级成员访问
 """

实现这些权限的一种方法是创建多个装饰器,例如:

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):
 """
 删除具有给定Id的用户,只有具有管理员权限的用户才能访问此函数
 """
@requires_logged_in 
def new_game():
 """
 任何已登录的用户都可以启动一个新游戏
 """
@requires_premium_member
def premium_checkpoint():
 """
 保存游戏进程,只允许高级成员访问
 """

但这太可怕了。它需要大量的复制粘贴,并且每个装饰器需要不同的名称,如果对权限的检查方式进行了任何更改,则必须更新每个装饰器。有一个装饰器可以完成这三个工作不是很好吗?

为此,我们需要一个返回装饰器的函数:

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): #这样装饰器就不会抛出NameError
 return ['logged_in',]
def current_user_id(): #名称错误也是如此
 return 1
#现在我们可以进行装饰了 
@requires_permission('administrator')
def delete_user(iUserId):
 """
 删除具有给定Id的用户,只有具有管理员权限的用户才能访问此函数
 """
@requires_permission('logged_in')
def new_game():
 """
 任何已登录的用户都可以启动一个新游戏
 """
@requires_permission('premium_member')
def premium_checkpoint():
 """
 保存游戏进程,只允许高级成员访问
 """

尝试调用delete_user,new_game和premium_checkpoint看看会发生什么。

premium_checkpoint和delete_user都在消息“权限被拒绝”的情况下引发异常,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):
 ...
 @time_this
 def do_stuff_2(self):
 ...
 @time_this
 def do_stuff_3(self):
 ...

这样就可以了。但是这个类中还有一些额外的代码行。如果我们写一些更多的类方法并忘记装饰它们中的一个呢?如果我们决定不再为进行计时怎么办?这里肯定存在人为错误的空间。这样编写它会好得多:

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

如你所知,该代码相当于:

class ImportantStuff:
 def do_stuff_1(self):
 ...
 def do_stuff_2(self):
 ...
 def do_stuff_3(self):
 ...
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(object):
 def __init__(self,*args,**kwargs):
 self.oInstance = Cls(*args,**kwargs)
 def __getattribute__(self,s):
 """
 每当访问NewCls对象的任何属性时,都会调用这个函数。这个函数首先尝试
 从NewCls获取属性。如果失败,则尝试从self获取属性。oInstance(一个
 修饰类的实例)。如果它设法从self获取属性。oInstance,
 属性是一个实例方法,然后应用' time_this '。
 """
 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) # 这等价于用time_this修饰方法
 else:
 return x
 return NewCls
#现在让我们做一个虚拟类来测试它:
@time_all_class_methods
class Foo(object):
 def a(self):
 print ("entering a")
 import time
 time.sleep(3)
 print ("exiting a")
oF = Foo()
oF.a()

结论

在装饰器的高级用法中,我向你展示了使用Python装饰器的一些技巧 - 我已经向你展示了如何将参数传递给装饰器,以及如何装饰类。但这仍然只是冰山的一角。在各种奇怪的情况下,有大量的方法用于装饰器。你甚至可以装饰你的装饰器(但如果你到达那一点,那么做一个全面的检查可能是个好主意)。Python同时内置了一些值得了解的装饰器,例如装饰器staticmethod及classmethod。

接下来要怎么做?除了我在这篇文章中向你展示的内容外,通常不需要对装饰器执行任何更复杂的操作。如果你对更改类功能的更多方法感兴趣,那么我建议阅读有关继承和一般OO设计原则的数据。或者,如果你真的想学会他们,那么请阅读元类(但同样,处理这些东西几乎不需要)。



Tags:Python装饰器   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
装饰器(Decorators)是 Python 的一个重要部分。简单地说:他们是修改其他函数的功能的函数。他们有助于让我们的代码更简短,也更Pythonic(Python范儿)。在程序开发中经常使用...【详细内容】
2019-10-11  Tags: Python装饰器  点击:(115)  评论:(0)  加入收藏
装饰器给我们带来高效的编程,但也会产生一些问题,而这些问题又容易被我们大家忽视。装饰器产生的副作用在Python世界里,一切皆对象,所以函数也是对象,函数有一些内置属性,例如:__na...【详细内容】
2019-08-29  Tags: Python装饰器  点击:(202)  评论:(0)  加入收藏
介绍首先我要承认,装饰器非常难!你在本教程中看到的一些代码将会有一些复杂。大多数人在学习Python时都跟装饰器做过斗争,所以如果这对你来说很奇怪,不要感到沮丧,因为同样的大...【详细内容】
2019-07-01  Tags: Python装饰器  点击:(296)  评论:(0)  加入收藏
▌简易百科推荐
大家好,我是菜鸟哥,今天跟大家一起聊一下Python4的话题! 从2020年的1月1号开始,Python官方正式的停止了对于Python2的维护。Python也正式的进入了Python3的时代。而随着时间的...【详细内容】
2021-12-28  菜鸟学python    Tags:Python4   点击:(1)  评论:(0)  加入收藏
学习Python的初衷是因为它的实践的便捷性,几乎计算机上能完成的各种操作都能在Python上找到解决途径。平时工作需要在线学习。而在线学习的复杂性经常让人抓狂。费时费力且效...【详细内容】
2021-12-28  风度翩翩的Python    Tags:Python   点击:(1)  评论:(0)  加入收藏
Python 是一个很棒的语言。它是世界上发展最快的编程语言之一。它一次又一次地证明了在开发人员职位中和跨行业的数据科学职位中的实用性。整个 Python 及其库的生态系统使...【详细内容】
2021-12-27  IT资料库    Tags:Python 库   点击:(2)  评论:(0)  加入收藏
菜单驱动程序简介菜单驱动程序是通过显示选项列表从用户那里获取输入并允许用户从选项列表中选择输入的程序。菜单驱动程序的一个简单示例是 ATM(自动取款机)。在交易的情况下...【详细内容】
2021-12-27  子冉爱python    Tags:Python   点击:(4)  评论:(0)  加入收藏
有不少同学学完Python后仍然很难将其灵活运用。我整理15个Python入门的小程序。在实践中应用Python会有事半功倍的效果。01 实现二元二次函数实现数学里的二元二次函数:f(x,...【详细内容】
2021-12-22  程序汪小成    Tags:Python入门   点击:(32)  评论:(0)  加入收藏
Verilog是由一个个module组成的,下面是其中一个module在网表中的样子,我只需要提取module名字、实例化关系。module rst_filter ( ...); 端口声明... wire定义......【详细内容】
2021-12-22  编程啊青    Tags:Verilog   点击:(8)  评论:(0)  加入收藏
运行环境 如何从 MP4 视频中提取帧 将帧变成 GIF 创建 MP4 到 GIF GUI ...【详细内容】
2021-12-22  修道猿    Tags:Python   点击:(6)  评论:(0)  加入收藏
面向对象:Object Oriented Programming,简称OOP,即面向对象程序设计。类(Class)和对象(Object)类是用来描述具有相同属性和方法对象的集合。对象是类的具体实例。比如,学生都有...【详细内容】
2021-12-22  我头秃了    Tags:python   点击:(9)  评论:(0)  加入收藏
所谓内置函数,就是Python提供的, 可以直接拿来直接用的函数,比如大家熟悉的print,range、input等,也有不是很熟,但是很重要的,如enumerate、zip、join等,Python内置的这些函数非常...【详细内容】
2021-12-21  程序员小新ds    Tags:python初   点击:(5)  评论:(0)  加入收藏
Hi,大家好。我们在接口自动化测试项目中,有时候需要一些加密。今天给大伙介绍Python实现各种 加密 ,接口加解密再也不愁。目录一、项目加解密需求分析六、Python加密库PyCrypto...【详细内容】
2021-12-21  Python可乐    Tags:Python   点击:(8)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条