原文地址
https://www.codementor.io/python/tutorial/advanced-use-python-decorators-class-function
介绍
我写这篇文章的主要目的是介绍装饰器的高级用法。如果你对装饰器知之甚少,或者对本文讲到的知识点易混淆。我建议你复习下装饰器基础教程。
本教程的目标是介绍装饰器的一些有趣的用法。特别是怎样在类中使用装饰器,怎样给装饰器传递额外的参数。
装饰器 vs 装饰器模式
Decorator模式是一个面向对象的设计模式,它允许动态地往现有的对象添加行为。当你装饰了一个对象,在某种程度上,你是在独立于同一个类的其他实例的基础上扩展其功能。
Python装饰器不是装饰器模式的实现,它在函数、方法定义的时候添加功能,而不是在运行的时候添加。Decorator设计模式本身可以在Python中实现,因为Python是动态编程语言,所以没有必要这样做。
一个基础的装饰器
这是装饰器的最简单例子,在继续往下面阅读之前请确保理解此段代码。如果你需要更多关于此代码的解释,请复习下基础装饰器教程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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中经常有用到,例如:
1
2
3
|
@view_config (route_name = 'home' , renderer = 'templates/mytemplate.pt' )
def my_view(request):
return { 'project' : 'hello decorators' }
|
比方说,我们有一个用户可以登录并且可以和用户交互的GUI应用程序。用户和GUI界面的交互触发事件,导致Python函数执行。假设有许多使用该图形界面的用户,他们各自的权限级别差异很大,不同的功能执行需要不同的权限。比如,考虑以下功能:
1
2
3
4
5
6
7
8
9
10
11
12
|
# 假设这些函数是存在的
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 """
|
一种实现这些权限检查的方式是实现多个装饰器,比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
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 """
|
但是,这太可怕了。这需要大量的复制粘贴,每个装饰器需要一个不同的名字,如果有任何关于权限检查的改变,每个装饰器都需要修改。就没有一个装饰器把以上三个装饰器的工作都干了的吗?
为了解决此问题,我们需要一个返回装饰器的函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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_user
,new name
和premium_checkpoint
然后看看发生了什么。premium_checkpoint
和delete_user
产生了一个“permission denied”
的异常,new_game
执行正常。
下面是带参数装饰的一般形式,和例子的使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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()
|
等价于:
1
2
3
4
5
6
7
8
9
10
11
12
|
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
装饰此类:
1
2
3
4
5
6
7
8
9
10
|
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
|
此方法可以运行正常。但是在该类中存在许多多余的代码,如果我们想建立更多的类方法并且遗忘了装饰其中的一个方法,如果我们不想装饰该类中的方法了,会发生什么样的情况呢?这可能会存在出现认为错误的空间,如果写成这样会更有好:
1
2
3
4
5
6
7
8
|
@time_all_class_methods
class ImportantStuff:
def do_stuff_1( self ):
pass
def do_stuff_2( self ):
pass
def do_stuff_3( self ):
pass
|
等价于:
1
2
3
4
5
6
7
8
|
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
功能一样。也就是说,我们仍然希望做重要的事情,我们希望记录下每个步骤发生的时间。我们写成这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
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有一些内置的装饰器,比如:staticmethod
,classmethod
阅读完本文还需要学习什么呢?通常是没有比我在文章中展示的装饰器更复杂的了,如果你有兴趣学习更多关于改变类功能的方法,我建议您阅读下继承和OOP设计原则。或者你可以试试阅读一下元类。
以上就是深入了解Python装饰器的高级用法的详细内容,更多关于Python装饰器的资料请关注服务器之家其它相关文章!
原文链接:https://www.jianshu.com/p/17cf262fc693