Python 裡的所有東西都是 object
function 也是
所以你可以對 function 做任何跟 object 一樣的事
例如把一個 function 當成參數丟給另一個 function
當然也可以 decorate class
不帶參數的 decorator
第一層 def 接收 func
第二層 def 接收 func 的 *args, **kwargs
通常意義下的 decorator 是把 func
(就是 something_1、something_2)丟給 decorator function
做一些額外的動作
然後回傳該 func
原本的 return
並不操作 func
本身
如果要操作 func
本身
例如幫 func
增加一個 attribute
請參考下下面的例子
def func_wrapper(func):
def arg_wrapper(*args, **kwargs):
print func.__name__ + ' was called'
print args
print kwargs
return func(*args, **kwargs)
return arg_wrapper
@func_wrapper
def something_1(name='[name]'):
print 'something_1: ' + name
@func_wrapper
def something_2(name='[name]'):
print 'something_2: ' + name
something_1('Tom')
something_2('Cathy')
something_1()
something_2()
ref:
http://www.dotblogs.com.tw/rickyteng/archive/2013/11/06/126852.aspx
帶參數的 decorator
需要傳參數給 decorator 時
要多 wrap 一次
其實就是在原本的 decorator function 的外面再多加一個 def
來接收參數
那個 model_class
就是傳給 decorator 的參數
# 跟不帶參數的 decorator 相比多了一層,用來接收 decorator 的參數
def can_access_item_required(model_class):
def func_wrapper(func):
def arg_wrapper(*args, **kwargs):
request = args[0]
item = model_class.objects.get(pk=kwargs['pk'])
if not model_class.objects.can_access_item_by(item, request.user):
raise PermissionDenied()
return func(*args, **kwargs)
return arg_wrapper
return func_wrapper
@can_access_item_required(Label)
def label_update(request, pk):
pass
# equals to
label_update = can_access_item_required(Artist)(label_update)
ref:
https://www.python.org/dev/peps/pep-0318/#current-syntax
decorator 修改 func 本身
不需要封裝什麼
單純地把 func
丟給另一個 function
然後再 return 那個 func
即可
in views.py
def intro_middleware_decorator(func):
func.enable_intro_middleware = False
return func
@intro_middleware_decorator
def intro_1(request):
return render(request, 'dps/intro/1.html')
in middleware.py
class IntroMiddleware(object):
"""
如果 user 沒有完成 intro 步驟,則轉址到 /intro/1/
"""
def __init__(self):
self.enable = True
def process_view(self, request, view_func, view_args, view_kwargs):
"""
要在 intro 系列的 view 加上 disable_intro_middleware_decorator
避免無限迴圈
"""
self.enable = getattr(view_func, 'enable_intro_middleware', True)
def process_response(self, request, response):
if self.enable:
resolver = request.resolver_match
namespace = getattr(resolver, 'namespace', None)
# 避免 admin, login 之類的 view 也都被 redirect
if namespace == 'dps':
if request.user.is_authenticated():
profile = request.user.profile
if (profile.identity == ProfileIdentity.PHANTOM) and (not profile.is_ready):
return redirect('dps:intro-one')
else:
return redirect('auth:login')
return response
decorator in a Class
class SVAPIListView(object):
@staticmethod
def filter_last_modified_decorator(func):
"""
用來讓 client 同步資料
?last_modified=2015-02-04T15:16:22&is_deleted=true
可以拿到某個時間點之後,新增、修改、刪除的項目
必須明確地指定 is_deleted=true 才會包含被刪除的項目
"""
def _add_filters_for_syncing(self, *args, **kwargs):
queryset = func(self, *args, **kwargs)
last_modified = self.get_last_modified()
if last_modified:
queryset = queryset.filter(last_modified__gte=last_modified)
is_deleted = self.get_is_deleted()
target_user = self.get_target_user()
if (self.request.user == target_user) and (is_deleted):
# 只有用戶本人才能拿到被刪除的 items
pass
else:
queryset = queryset.filter(enable=True)
else:
queryset = queryset.filter(enable=True)
return queryset
return _add_filters_for_syncing
class UserAlbumList(SVAPIListView):
serializer_class = api_serializers.AlbumListSerializer
@UserIDAsUsernameMixin.filter_last_modified_decorator
def get_queryset(self, profile_user):
queryset = MusicAlbum.objects.filter(user=profile_user)
queryset = queryset.select_related('user', 'user__profile')
ordering = self.request.GET.get('ordering')
if ordering == 'like_count':
queryset = queryset.order_by('-like_count')
else:
queryset = queryset.order_by('-id')
return queryset
def get(self, request, user_id, *args, **kwargs):
profile_user = self.get_target_user()
queryset = self.get_queryset(profile_user=profile_user)
data = self.get_serializer_data(queryset)
return Response(data)
ref:
http://stackoverflow.com/questions/1263451/python-decorators-in-classes
多個 decorator 時的執行順序
如果有多個 decorator 裝飾同一個 function / class
執行的順序是由下往上的
會先執行 @decorator_1
@decorator_3
@decorator_2
@decorator_1
def function():
pass
Class-based decorator
class RecorderDecorator(object):
__slots__ = ['recorder', 'msg_template', 'subject', 'action']
recorder = Recorder()
def __init__(self, msg_template, subject=None, action=None):
self.msg_template = msg_template
self.subject = subject
self.action = action
class model_recorder(RecorderDecorator):
"""
用於 model 的 method
接受一個 string template,會自動使用 method parameters 作為 template context
會使用 method name 作為 action,model name 作為 subject
會在裝飾的 method 執行完之後執行這個 decorator
用法可以參考 Song.play()
@recorder_decorators.model_recorder('user: {user.id}, ip: {from_ip}, full: {is_full}, embed: {is_embed}')
def play(self, user, from_ip, is_full=False, is_embed=False, from_playlist=None):
pass
則 log message 會是 [song:play] user: 123, ip: 59.120.12.57, full: True, embed: False
"""
def __call__(self, func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
func_result = func(*args, **kwargs)
func_kwargs = inspect.getcallargs(func, *args, **kwargs)
model_instance = func_kwargs.get('self')
if getattr(model_instance, 'id'):
new_msg_template = ', '.join(('item: {self.id}', self.msg_template))
else:
new_msg_template = self.msg_template
if not self.subject:
model_name = type_utils.item_type(model_instance)
new_subject = model_name
else:
new_subject = self.subject
if not self.action:
new_action = func.__name__
else:
new_action = self.action
msg = new_msg_template.format(**func_kwargs)
self.recorder.write(new_subject, new_action, msg)
return func_result
return wrapper
class Song(models.Model):
@model_recorder('user: {user.id}, ip: {from_ip}, full: {is_full}, embed: {is_embed}')
def play(self, user, from_ip, is_full=False, is_embed=False, from_playlist=None):
pass
ref:
https://stackoverflow.com/questions/9416947/python-class-based-decorator-with-parameters-that-can-decorate-a-method-or-a-fun