{"id":165,"date":"2015-05-27T18:54:55","date_gmt":"2015-05-27T10:54:55","guid":{"rendered":"http:\/\/vinta.ws\/code\/?p=165"},"modified":"2026-02-18T01:20:36","modified_gmt":"2026-02-17T17:20:36","slug":"decorator-in-python","status":"publish","type":"post","link":"https:\/\/vinta.ws\/code\/decorator-in-python.html","title":{"rendered":"Python decorators"},"content":{"rendered":"<p>Python \u88e1\u7684\u6240\u6709\u6771\u897f\u90fd\u662f object<br \/>\nfunction \u4e5f\u662f<br \/>\n\u6240\u4ee5\u4f60\u53ef\u4ee5\u5c0d function \u505a\u4efb\u4f55\u8ddf object \u4e00\u6a23\u7684\u4e8b<br \/>\n\u4f8b\u5982\u628a\u4e00\u500b function \u7576\u6210\u53c3\u6578\u4e1f\u7d66\u53e6\u4e00\u500b function<br \/>\n\u7576\u7136\u4e5f\u53ef\u4ee5 decorate class<\/p>\n<h2>\u4e0d\u5e36\u53c3\u6578\u7684 decorator<\/h2>\n<p>\u7b2c\u4e00\u5c64 def \u63a5\u6536 func<br \/>\n\u7b2c\u4e8c\u5c64 def \u63a5\u6536 func \u7684 *args, **kwargs<\/p>\n<p>\u901a\u5e38\u610f\u7fa9\u4e0b\u7684 decorator \u662f\u628a <code>func<\/code>\uff08\u5c31\u662f something_1\u3001something_2\uff09\u4e1f\u7d66 decorator function<br \/>\n\u505a\u4e00\u4e9b\u984d\u5916\u7684\u52d5\u4f5c<br \/>\n\u7136\u5f8c\u56de\u50b3\u8a72 <code>func<\/code> \u539f\u672c\u7684 return<br \/>\n\u4e26\u4e0d\u64cd\u4f5c <code>func<\/code> \u672c\u8eab<\/p>\n<p>\u5982\u679c\u8981\u64cd\u4f5c <code>func<\/code> \u672c\u8eab<br \/>\n\u4f8b\u5982\u5e6b <code>func<\/code> \u589e\u52a0\u4e00\u500b attribute<br \/>\n\u8acb\u53c3\u8003\u4e0b\u4e0b\u9762\u7684\u4f8b\u5b50<\/p>\n<pre class=\"line-numbers\"><code class=\"language-py\">def func_wrapper(func):\n    def arg_wrapper(*args, **kwargs):\n        print func.__name__ + ' was called'\n        print args\n        print kwargs\n\n        return func(*args, **kwargs)\n\n    return arg_wrapper\n\n@func_wrapper\ndef something_1(name='[name]'):\n    print 'something_1: ' + name\n\n@func_wrapper\ndef something_2(name='[name]'):\n    print 'something_2: ' + name\n\nsomething_1('Tom')\nsomething_2('Cathy')\nsomething_1()\nsomething_2()<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"http:\/\/www.dotblogs.com.tw\/rickyteng\/archive\/2013\/11\/06\/126852.aspx\">http:\/\/www.dotblogs.com.tw\/rickyteng\/archive\/2013\/11\/06\/126852.aspx<\/a><\/p>\n<h2>\u5e36\u53c3\u6578\u7684 decorator<\/h2>\n<p>\u9700\u8981\u50b3\u53c3\u6578\u7d66 decorator \u6642<br \/>\n\u8981\u591a wrap \u4e00\u6b21<br \/>\n\u5176\u5be6\u5c31\u662f\u5728\u539f\u672c\u7684 decorator function \u7684\u5916\u9762\u518d\u591a\u52a0\u4e00\u500b <code>def<\/code> \u4f86\u63a5\u6536\u53c3\u6578<\/p>\n<p>\u90a3\u500b <code>model_class<\/code> \u5c31\u662f\u50b3\u7d66 decorator \u7684\u53c3\u6578<\/p>\n<pre class=\"line-numbers\"><code class=\"language-py\"># \u8ddf\u4e0d\u5e36\u53c3\u6578\u7684 decorator \u76f8\u6bd4\u591a\u4e86\u4e00\u5c64\uff0c\u7528\u4f86\u63a5\u6536 decorator \u7684\u53c3\u6578\ndef can_access_item_required(model_class):\n    def func_wrapper(func):\n        def arg_wrapper(*args, **kwargs):\n            request = args[0]\n            item = model_class.objects.get(pk=kwargs['pk'])\n\n            if not model_class.objects.can_access_item_by(item, request.user):\n                raise PermissionDenied()\n\n            return func(*args, **kwargs)\n\n        return arg_wrapper\n\n    return func_wrapper\n\n@can_access_item_required(Label)\ndef label_update(request, pk):\n    pass\n\n# equals to\n\nlabel_update = can_access_item_required(Artist)(label_update)<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/www.python.org\/dev\/peps\/pep-0318\/#current-syntax\">https:\/\/www.python.org\/dev\/peps\/pep-0318\/#current-syntax<\/a><\/p>\n<h2>decorator \u4fee\u6539 func \u672c\u8eab<\/h2>\n<p>\u4e0d\u9700\u8981\u5c01\u88dd\u4ec0\u9ebc<br \/>\n\u55ae\u7d14\u5730\u628a <code>func<\/code> \u4e1f\u7d66\u53e6\u4e00\u500b function<br \/>\n\u7136\u5f8c\u518d return \u90a3\u500b <code>func<\/code> \u5373\u53ef<\/p>\n<p>in views.py<\/p>\n<pre class=\"line-numbers\"><code class=\"language-py\">def intro_middleware_decorator(func):\n    func.enable_intro_middleware = False\n    return func\n\n@intro_middleware_decorator\ndef intro_1(request):\n    return render(request, 'dps\/intro\/1.html')<\/code><\/pre>\n<p>in middleware.py<\/p>\n<pre class=\"line-numbers\"><code class=\"language-py\">class IntroMiddleware(object):\n    \"\"\"\n    \u5982\u679c user \u6c92\u6709\u5b8c\u6210 intro \u6b65\u9a5f\uff0c\u5247\u8f49\u5740\u5230 \/intro\/1\/\n    \"\"\"\n    def __init__(self):\n        self.enable = True\n    def process_view(self, request, view_func, view_args, view_kwargs):\n        \"\"\"\n        \u8981\u5728 intro \u7cfb\u5217\u7684 view \u52a0\u4e0a disable_intro_middleware_decorator\n        \u907f\u514d\u7121\u9650\u8ff4\u5708\n        \"\"\"\n        self.enable = getattr(view_func, 'enable_intro_middleware', True)\n    def process_response(self, request, response):\n        if self.enable:\n            resolver = request.resolver_match\n            namespace = getattr(resolver, 'namespace', None)\n            # \u907f\u514d admin, login \u4e4b\u985e\u7684 view \u4e5f\u90fd\u88ab redirect\n            if namespace == 'dps':\n                if request.user.is_authenticated():\n                    profile = request.user.profile\n                    if (profile.identity == ProfileIdentity.PHANTOM) and (not profile.is_ready):\n                        return redirect('dps:intro-one')\n                else:\n                    return redirect('auth:login')\n        return response<\/code><\/pre>\n<h2>decorator in a Class<\/h2>\n<pre class=\"line-numbers\"><code class=\"language-py\">class SVAPIListView(object):\n\n    @staticmethod\n    def filter_last_modified_decorator(func):\n        \"\"\"\n        \u7528\u4f86\u8b93 client \u540c\u6b65\u8cc7\u6599\n        ?last_modified=2015-02-04T15:16:22&amp;is_deleted=true\n        \u53ef\u4ee5\u62ff\u5230\u67d0\u500b\u6642\u9593\u9ede\u4e4b\u5f8c\uff0c\u65b0\u589e\u3001\u4fee\u6539\u3001\u522a\u9664\u7684\u9805\u76ee\n        \u5fc5\u9808\u660e\u78ba\u5730\u6307\u5b9a is_deleted=true \u624d\u6703\u5305\u542b\u88ab\u522a\u9664\u7684\u9805\u76ee\n        \"\"\"\n\n        def _add_filters_for_syncing(self, *args, **kwargs):\n            queryset = func(self, *args, **kwargs)\n\n            last_modified = self.get_last_modified()\n            if last_modified:\n                queryset = queryset.filter(last_modified__gte=last_modified)\n\n                is_deleted = self.get_is_deleted()\n                target_user = self.get_target_user()\n                if (self.request.user == target_user) and (is_deleted):\n                    # \u53ea\u6709\u7528\u6236\u672c\u4eba\u624d\u80fd\u62ff\u5230\u88ab\u522a\u9664\u7684 items\n                    pass\n                else:\n                    queryset = queryset.filter(enable=True)\n            else:\n                queryset = queryset.filter(enable=True)\n\n            return queryset\n\n        return _add_filters_for_syncing\n\nclass UserAlbumList(SVAPIListView):\n    serializer_class = api_serializers.AlbumListSerializer\n\n    @UserIDAsUsernameMixin.filter_last_modified_decorator\n    def get_queryset(self, profile_user):\n        queryset = MusicAlbum.objects.filter(user=profile_user)\n        queryset = queryset.select_related('user', 'user__profile')\n\n        ordering = self.request.GET.get('ordering')\n        if ordering == 'like_count':\n            queryset = queryset.order_by('-like_count')\n        else:\n            queryset = queryset.order_by('-id')\n\n        return queryset\n\n    def get(self, request, user_id, *args, **kwargs):\n        profile_user = self.get_target_user()\n        queryset = self.get_queryset(profile_user=profile_user)\n        data = self.get_serializer_data(queryset)\n\n        return Response(data)<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"http:\/\/stackoverflow.com\/questions\/1263451\/python-decorators-in-classes\">http:\/\/stackoverflow.com\/questions\/1263451\/python-decorators-in-classes<\/a><\/p>\n<h2>\u591a\u500b decorator \u6642\u7684\u57f7\u884c\u9806\u5e8f<\/h2>\n<p>\u5982\u679c\u6709\u591a\u500b decorator \u88dd\u98fe\u540c\u4e00\u500b function \/ class<br \/>\n\u57f7\u884c\u7684\u9806\u5e8f\u662f\u7531\u4e0b\u5f80\u4e0a\u7684<br \/>\n\u6703\u5148\u57f7\u884c <code>@decorator_1<\/code><\/p>\n<pre class=\"line-numbers\"><code class=\"language-py\">@decorator_3\n@decorator_2\n@decorator_1\ndef function():\n    pass<\/code><\/pre>\n<h2>Class-based decorator<\/h2>\n<pre class=\"line-numbers\"><code class=\"language-py\">class RecorderDecorator(object):\n\n    __slots__ = ['recorder', 'msg_template', 'subject', 'action']\n\n    recorder = Recorder()\n\n    def __init__(self, msg_template, subject=None, action=None):\n        self.msg_template = msg_template\n        self.subject = subject\n        self.action = action\n\nclass model_recorder(RecorderDecorator):\n    \"\"\"\n    \u7528\u65bc model \u7684 method\n    \u63a5\u53d7\u4e00\u500b string template\uff0c\u6703\u81ea\u52d5\u4f7f\u7528 method parameters \u4f5c\u70ba template context\n    \u6703\u4f7f\u7528 method name \u4f5c\u70ba action\uff0cmodel name \u4f5c\u70ba subject\n    \u6703\u5728\u88dd\u98fe\u7684 method \u57f7\u884c\u5b8c\u4e4b\u5f8c\u57f7\u884c\u9019\u500b decorator\n\n    \u7528\u6cd5\u53ef\u4ee5\u53c3\u8003 Song.play()\n\n    @recorder_decorators.model_recorder('user: {user.id}, ip: {from_ip}, full: {is_full}, embed: {is_embed}')\n    def play(self, user, from_ip, is_full=False, is_embed=False, from_playlist=None):\n        pass\n\n    \u5247 log message \u6703\u662f [song:play] user: 123, ip: 59.120.12.57, full: True, embed: False\n    \"\"\"\n\n    def __call__(self, func):\n        @functools.wraps(func)\n        def wrapper(*args, **kwargs):\n            func_result = func(*args, **kwargs)\n            func_kwargs = inspect.getcallargs(func, *args, **kwargs)\n\n            model_instance = func_kwargs.get('self')\n\n            if getattr(model_instance, 'id'):\n                new_msg_template = ', '.join(('item: {self.id}', self.msg_template))\n            else:\n                new_msg_template = self.msg_template\n\n            if not self.subject:\n                model_name = type_utils.item_type(model_instance)\n                new_subject = model_name\n            else:\n                new_subject = self.subject\n\n            if not self.action:\n                new_action = func.__name__\n            else:\n                new_action = self.action\n\n            msg = new_msg_template.format(**func_kwargs)\n            self.recorder.write(new_subject, new_action, msg)\n\n            return func_result\n\n        return wrapper\n\nclass Song(models.Model):\n\n    @model_recorder('user: {user.id}, ip: {from_ip}, full: {is_full}, embed: {is_embed}')\n    def play(self, user, from_ip, is_full=False, is_embed=False, from_playlist=None):\n        pass<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/stackoverflow.com\/questions\/9416947\/python-class-based-decorator-with-parameters-that-can-decorate-a-method-or-a-fun\">https:\/\/stackoverflow.com\/questions\/9416947\/python-class-based-decorator-with-parameters-that-can-decorate-a-method-or-a-fun<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Python \u88e1\u7684\u6240\u6709\u6771\u897f\u90fd\u662f object function \u4e5f\u662f \u6240\u4ee5\u4f60\u53ef\u4ee5\u5c0d function \u505a\u4efb\u4f55\u8ddf object \u4e00\u6a23\u7684\u4e8b \u4f8b\u5982\u628a\u4e00\u500b function \u7576\u6210\u53c3\u6578\u4e1f\u7d66\u53e6\u4e00\u500b function \u7576\u7136\u4e5f\u53ef\u4ee5 decorate class \u4e0d\u5e36\u53c3\u6578\u7684 decorator \u7b2c\u4e00\u5c64 def \u63a5\u6536 func \u7b2c\u4e8c\u5c64 def \u63a5\u6536 func \u7684 *args, **kwargs \u901a\u5e38\u610f\u7fa9\u4e0b\u7684 decorator \u662f\u628a func\uff08\u5c31\u662f something_1\u3001something_2\uff09\u4e1f\u7d66 decorator function \u505a\u4e00\u4e9b\u984d\u5916\u7684\u52d5\u4f5c \u7136\u5f8c\u56de\u50b3\u8a72 func \u539f\u672c\u7684 return \u4e26\u4e0d\u64cd\u4f5c func \u672c\u8eab \u5982\u679c\u8981\u64cd\u4f5c func \u672c\u8eab \u4f8b\u5982\u5e6b func \u589e\u52a0\u4e00\u500b attribute \u8acb\u53c3\u8003\u4e0b\u4e0b\u9762\u7684\u4f8b\u5b50 def func_wrapper(func):&hellip; <a href=\"https:\/\/vinta.ws\/code\/decorator-in-python.html\" class=\"more-link\">Read More<\/a><\/p>\n","protected":false},"author":1,"featured_media":775,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[2],"class_list":["post-165","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-about-python","tag-python"],"_links":{"self":[{"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/posts\/165","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/comments?post=165"}],"version-history":[{"count":0,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/posts\/165\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/media\/775"}],"wp:attachment":[{"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/media?parent=165"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/categories?post=165"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/tags?post=165"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}