Python decorator implements the DRY (no repeated code) principle, pythondry

Source: Internet
Author: User
Tags python decorator

Python decorator implements the DRY (no repeated code) principle, pythondry

The Python decorator is a powerful tool for eliminating redundancy. As you modularize a function into an appropriate size method, the decorator Can make it a concise function even for the most complex workflow.

For example, let's take a look at the Django web framework. This framework receives a method object for processing requests and returns a response object:

def handle_request(request):  return HttpResponse("Hello, World")

In a recent case, I need to write several api methods that meet the following conditions:

  • Return json response
  • If the request is a GET request, the error code is returned.

As an example of registering an api endpoint, I will write it like this:

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

However, I will write the json response and error return code in each api method. This will lead to a large number of logical duplicates. So let's try to use the decorator to implement the DRY principle.

Decorator Introduction

If you are not familiar with the decorator, I can explain it briefly. In fact, the decorator is an effective function package. When the python interpreter loads a function, it will execute the package, the package can modify the receiving parameters and return values of the function. For example, if I want to always return an integer greater than the actual return value, I can write the decorator as follows:

# 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.

Now we can use the @ symbol to decorate another function:

@incrementdef plus(a, b):  return a + b result = plus(4, 6)assert(result == 11, "We wrote our decorator wrong!")

The decorator modifies the existing function and assigns the result returned by the decorator to the variable. In this example, the result of 'plus 'actually points to the result of increment (plus.

Error returned for non-post requests

Now let's apply decorator in some more useful scenarios. If the POST request is not received in django, an error response is returned using the decorator.

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

Now we can apply this modifier in the above registration api:

@post_onlydef 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

Now we have a decorator that can be reused in each api method.

Send json response

To send a json response (processing status code 500 at the same time), we can create another decorator:

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

Now we can remove json-related code in the original method and add a decorator as a replacement:

@post_only@json_responsedef 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) }

Now, if I need to write a new method, I can use the decorator for redundancy. If I want to write the logon method, I only need to write the code with positive correlation:

@post_only@json_responsedef 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: parameterize your request Method

I have used the Tubogears framework, and I like it very much when request parameters are directly interpreted and transferred to methods. So how can we imitate this feature in Django? Well, the decorator is a solution!

For example:

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

Note that this is an example of a parameterized modifier. In this example, the result of the function is the actual decorator.

Now I can write the method with the parameterized decorator! I can even choose whether to allow GET and POST, or only one request parameter type.

@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}

Now we have a simple and easy-to-understand api.

BONUS #2: Use functools. wraps to save docstrings and function names

Unfortunately, one side effect of using the decorator is that the method name (name) and docstring (doc) values are not saved:

def increment(f):  """ Increment a function result """  wrapped_f(a, b):    return f(a, b) + 1  return wrapped_f@incrementdef plus(a, b)  """ Add two things together """  return a + bplus.__name__ # this is now 'wrapped_f' instead of 'plus'plus.__doc__  # this now returns 'Increment a function result' instead of 'Add two things together'

This will cause trouble for applications that use reflection, such as Sphinx, an application that automatically generates documents.

To solve this problem, we can append the name and docstring with the 'wraps' modifier:

from functools import wrapsdef increment(f):  """ Increment a function result """  @wraps(f)  wrapped_f(a, b):    return f(a, b) + 1  return wrapped_f@incrementdef 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

If you take a closer look at the above method of using the decorator, there are many duplicates in the package declaration and return.

You can installpython egg ‘decorator'The decorator contains a decorator template!

Use easy_install:

$ sudo easy_install decorator

Or Pip:

$ pip install decorator

Then you can write the following code:

from decorator import decorator@decoratordef 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)

This decorator saves the return values of name and doc, that is, it encapsulates

Functools. wraps!

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.