Python装饰器是一个消除冗余的强大工具。随着将功能模块化为大小合适的方法,即使是最复杂的工作流,装饰器也能使它变成简洁的功能。
例如让我们看看Django web框架,该框架处理请求的方法接收一个方法对象,返回一个响应对象:
def handle_request(request): return HttpResponse("Hello, World")
我最近遇到一个案例,需要编写几个满足下述条件的api方法:
- 返回json响应
- 如果是GET请求,那么返回错误码
做为一个注册api端点例子,我将会像这样编写:
def register(request): result = None # check for post only if request.method != 'POST': result = {"error": "this method only accepts posts!"} else: try: user = User.objects.create_user(request.POST['username'], request.POST['email'], request.POST['password']) # optional fields for field in ['first_name', 'last_name']: if field in request.POST: setattr(user, field, request.POST[field]) user.save() result = {"success": True} except KeyError as e: result = {"error": str(e) } response = HttpResponse(json.dumps(result)) if "error" in result: response.status_code = 500 return response
然而这样我将会在每个api方法中编写json响应和错误返回的代码。这将会导致大量的逻辑重复。所以让我们尝试用装饰器实现DRY原则吧。
装饰器简介
如果你不熟悉装饰器,我可以简单解释一下,实际上装饰器就是有效的函数包装器,python解释器加载函数的时候就会执行包装器,包装器可以修改函数的接收参数和返回值。举例来说,如果我想要总是返回比实际返回值大一的整数结果,我可以这样写装饰器:
# a decorator receives the method it's wrapping as a variable 'f' def increment(f): # we use arbitrary args and keywords to # ensure we grab all the input arguments. def wrapped_f(*args, **kw): # note we call f against the variables passed into the wrapper, # and cast the result to an int and increment . return int(f(*args, **kw)) + 1 return wrapped_f # the wrapped function gets returned.
现在我们就可以用@符号和这个装饰器去装饰另外一个函数了:
@increment def plus(a, b): return a + b result = plus(4, 6) assert(result == 11, "We wrote our decorator wrong!")
装饰器修改了存在的函数,将装饰器返回的结果赋值给了变量。在这个例子中,'plus'的结果实际指向increment(plus)的结果。
对于非post请求返回错误
现在让我们在一些更有用的场景下应用装饰器。如果在django中接收的不是POST请求,我们用装饰器返回一个错误响应。
def post_only(f): """ Ensures a method is post only """ def wrapped_f(request): if request.method != "POST": response = HttpResponse(json.dumps( {"error": "this method only accepts posts!"})) response.status_code = 500 return response return f(request) return wrapped_f
现在我们可以在上述注册api中应用这个装饰器:
@post_only def register(request): result = None try: user = User.objects.create_user(request.POST['username'], request.POST['email'], request.POST['password']) # optional fields for field in ['first_name', 'last_name']: if field in request.POST: setattr(user, field, request.POST[field]) user.save() result = {"success": True} except KeyError as e: result = {"error": str(e) } response = HttpResponse(json.dumps(result)) if "error" in result: response.status_code = 500 return response
现在我们就有了一个可以在每个api方法中重用的装饰器。
发送json响应
为了发送json响应(同时处理500状态码),我们可以新建另外一个装饰器:
def json_response(f): """ Return the response as json, and return a 500 error code if an error exists """ def wrapped(*args, **kwargs): result = f(*args, **kwargs) response = HttpResponse(json.dumps(result)) if type(result) == dict and 'error' in result: response.status_code = 500 return response
现在我们就可以在原方法中去除json相关的代码,添加一个装饰器做为代替:
post_only @json_response def register(request): try: user = User.objects.create_user(request.POST['username'], request.POST['email'], request.POST['password']) # optional fields for field in ['first_name', 'last_name']: if field in request.POST: setattr(user, field, request.POST[field]) user.save() return {"success": True} except KeyError as e: return {"error": str(e) }
现在,如果我需要编写新的方法,那么我就可以使用装饰器做冗余的工作。如果我要写登录方法,我只需要写真正相关的代码:
@post_only @json_response def login(request): if request.user is not None: return {"error": "User is already authenticated!"} user = auth.authenticate(request.POST['username'], request.POST['password']) if user is not None: if not user.is_active: return {"error": "User is inactive"} auth.login(request, user) return {"success": True, "id": user.pk} else: return {"error": "User does not exist with those credentials"}
BONUS: 参数化你的请求方法
我曾经使用过Tubogears框架,其中请求参数直接解释转递给方法这一点我很喜欢。所以要怎样在Django中模仿这一特性呢?嗯,装饰器就是一种解决方案!
例如:
def parameterize_request(types=("POST",)): """ Parameterize the request instead of parsing the request directly. Only the types specified will be added to the query parameters. e.g. convert a=test&b=cv in request.POST to f(a=test, b=cv) """ def wrapper(f): def wrapped(request): kw = {} if "GET" in types: for k, v in request.GET.items(): kw[k] = v if "POST" in types: for k, v in request.POST.items(): kw[k] = v return f(request, **kw) return wrapped return wrapper
注意这是一个参数化装饰器的例子。在这个例子中,函数的结果是实际的装饰器。
现在我就可以用参数化装饰器编写方法了!我甚至可以选择是否允许GET和POST,或者仅仅一种请求参数类型。
@post_only @json_response @parameterize_request(["POST"]) def register(request, username, email, password, first_name=None, last_name=None): user = User.objects.create_user(username, email, password) user.first_name=first_name user.last_name=last_name user.save() return {"success": True}
现在我们有了一个简洁的、易于理解的api。
BONUS #2: 使用functools.wraps保存docstrings和函数名
很不幸,使用装饰器的一个副作用是没有保存方法名(__name__)和docstring(__doc__)值:
def increment(f): """ Increment a function result """ wrapped_f(a, b): return f(a, b) + 1 return wrapped_f @increment def plus(a, b) """ Add two things together """ return a + b plus.__name__ # this is now 'wrapped_f' instead of 'plus' plus.__doc__ # this now returns 'Increment a function result' instead of 'Add two things together'
这将对使用反射的应用造成麻烦,比如Sphinx,一个 自动生成文档的应用。
为了解决这个问题,我们可以使用'wraps'装饰器附加上名字和docstring:
from functools import wraps def increment(f): """ Increment a function result """ @wraps(f) wrapped_f(a, b): return f(a, b) + 1 return wrapped_f @increment def plus(a, b) """ Add two things together """ return a + b plus.__name__ # this returns 'plus' plus.__doc__ # this returns 'Add two things together'
BONUS #3: 使用'decorator'装饰器
如果仔细看看上述使用装饰器的方式,在包装器声明和返回的地方也有不少重复。
你可以安装python egg 'decorator',其中包含一个提供装饰器模板的'decorator'装饰器!
使用easy_install:
$ sudo easy_install decorator
或者Pip:
$ pip install decorator
然后你可以简单的编写:
from decorator import decorator @decorator def post_only(f, request): """ Ensures a method is post only """ if request.method != "POST": response = HttpResponse(json.dumps( {"error": "this method only accepts posts!"})) response.status_code = 500 return response return f(request)
这个装饰器更牛逼的一点是保存了__name__和__doc__的返回值,也就是它封装了 functools.wraps的功能!

本篇文章给大家带来了关于Python的相关知识,其中主要介绍了关于Seaborn的相关问题,包括了数据可视化处理的散点图、折线图、条形图等等内容,下面一起来看一下,希望对大家有帮助。

本篇文章给大家带来了关于Python的相关知识,其中主要介绍了关于进程池与进程锁的相关问题,包括进程池的创建模块,进程池函数等等内容,下面一起来看一下,希望对大家有帮助。

本篇文章给大家带来了关于Python的相关知识,其中主要介绍了关于简历筛选的相关问题,包括了定义 ReadDoc 类用以读取 word 文件以及定义 search_word 函数用以筛选的相关内容,下面一起来看一下,希望对大家有帮助。

VS Code的确是一款非常热门、有强大用户基础的一款开发工具。本文给大家介绍一下10款高效、好用的插件,能够让原本单薄的VS Code如虎添翼,开发效率顿时提升到一个新的阶段。

本篇文章给大家带来了关于Python的相关知识,其中主要介绍了关于数据类型之字符串、数字的相关问题,下面一起来看一下,希望对大家有帮助。

本篇文章给大家带来了关于Python的相关知识,其中主要介绍了关于numpy模块的相关问题,Numpy是Numerical Python extensions的缩写,字面意思是Python数值计算扩展,下面一起来看一下,希望对大家有帮助。

pythn的中文意思是巨蟒、蟒蛇。1989年圣诞节期间,Guido van Rossum在家闲的没事干,为了跟朋友庆祝圣诞节,决定发明一种全新的脚本语言。他很喜欢一个肥皂剧叫Monty Python,所以便把这门语言叫做python。


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

Zend Studio 13.0.1
Powerful PHP integrated development environment

Notepad++7.3.1
Easy-to-use and free code editor

Atom editor mac version download
The most popular open source editor

SAP NetWeaver Server Adapter for Eclipse
Integrate Eclipse with SAP NetWeaver application server.

MinGW - Minimalist GNU for Windows
This project is in the process of being migrated to osdn.net/projects/mingw, you can continue to follow us there. MinGW: A native Windows port of the GNU Compiler Collection (GCC), freely distributable import libraries and header files for building native Windows applications; includes extensions to the MSVC runtime to support C99 functionality. All MinGW software can run on 64-bit Windows platforms.
