Five reasons why you should learn to use Python decorators

Python decorators are very simple to use. Anyone who can use Python functions can learn to use decorators:

Python

@somedecorator

def some_function():

    print("Check it out, I'm using decorators!")

Closures, but how to write decorators is another matter, and it's not easy. You have to understand the following:

How to use function as first-order parameter

Variable parameters

  • Parametric unpacking
  • Even some Python loading source code details

This knowledge takes a lot of time to understand and master. If you have accumulated a lot of other things to learn. Are these things worth learning?

For me, the answer is yes. I hope your answer is yes. So, what's the advantage of writing your own decorator? Or, what do they make easier for me in my daily development process?

Analysis, Logging and Means

For large applications, we often need to record the state of the application and measure the number of different activities. By wrapping these special events into functions or methods, the decorator can easily meet these requirements while ensuring code readability.

Python

from myapp.log import logger
def log_order_event(func):
    def wrapper(*args, **kwargs):
        logger.info("Ordering: %s", func.__name__)
        order = func(*args, **kwargs)
        logger.debug("Order result: %s", order.result)
        return order

    return wrapper

@log_order_event
def order_pizza(*toppings):
    # let's get some pizza!

Validation and runtime checking can also be used to count or record some other metrics.

Python is a strongly typed language, but the types of variables are dynamic. While this brings many benefits, it also means that bugs are easier to introduce. For static languages, such as Java, these bugs can be found at compile time. Therefore, you may want to have some custom checks on incoming or returned data. Decorators make it very easy for you to implement this requirement and apply it to multiple functions at once.

Imagine: You have many functions, each of which returns a dictionary type that contains a "summary" field. The value of this field cannot exceed the length of 80 characters. It would be a mistake to violate this requirement. The following decorator throws a ValueError exception when an error occurs:

Python

def validate_summary(func):
    def wrapper(*args, **kwargs):
        data = func(*args, **kwargs)
        if len(data["summary"]) > 80:
            raise ValueError("Summary too long")
        return data
    return wrapper


@validate_summary
def fetch_customer_data():
    # ...



@validate_summary
def query_orders(criteria):

    # ...


@validate_summary
def create_invoice(params):

    # ...

Once you have mastered how to write decorators, you can benefit greatly from the simple grammar they use, and you can add new semantics to the language to make it easier to use. The next best thing is that you can extend Python grammar yourself. Creating Framework

In fact, many open source frameworks use this approach. Flask is a Web application framework that uses decorators to route different URL s to different HTTP request functions:

Python

# For a RESTful todo-list API.

@app.route("/tasks/", methods=["GET"])
def get_all_tasks():
    tasks = app.store.get_all_tasks()
    return make_response(json.dumps(tasks), 200)



@app.route("/tasks/", methods=["POST"])
def create_task():
    payload = request.get_json(force=True)
    task_id = app.store.create_task(
        summary = payload["summary"],
        description = payload["description"],
    )

    task_info = {"id": task_id}
    return make_response(json.dumps(task_info), 201)


@app.route("/tasks/<int:task_id>/")
def task_details(task_id):
    task_info = app.store.task_details(task_id)
    if task_info is None:
        return make_response("", 404)
    return json.dumps(task_info)

In the ordinary use of Python process, we will also use decoration. For example, all objects depend on class methods and attribute decorators: here is a global object app, which has a route method. This route function returns a decorator to decorate the request handler. The processing behind this is very complex, but for programmers using Flask, all the complexity is hidden.

Python

class WeatherSimulation:

    def __init__(self, **params):
         self.params = params


    @classmethod
    def for_winter(cls, **other_params):
        params = {'month': 'Jan', 'temp': '0'}
        params.update(other_params)
        return cls(**params)


    @property
    def progress(self):
        return self.completed_iterations() / self.total_iterations()

Constructor is a simple method. This class has three different def statements, but each has different semantics:

  • for_winter is a class method
  • Programs is a dynamic read-only property

@ classmethod, Decorator and @property Decorators allow us to easily extend the semantics of Python objects in our normal use.

Reuse code that cannot be reused

Python provides powerful tools to wrap code in a reusable form, including functions, support for functional programming, and the idea that everything is object-oriented. However, there are still some codes that cannot be reused by using these tools.

Suppose there's a weird API. You can send requests in JSON format over HTTP, which works correctly 99.9% of the time. However, a small number of requests return the result of internal errors in the server. At this point, you need to resend the request. In this case, you need to implement retry logic, like this:

Python

resp = None
while True:
    resp = make_api_call()
    if resp.status_code == 500 and tries < MAX_TRIES:
        tries += 1
        continue
    break

process_response(resp)

Python now assumes that there are many calls to make_api_call in your code base, so do you need to implement this loop at every call? Does this loop need to be implemented every time a call is added? This pattern can hardly have a template code, unless you use a decorator, then it becomes very simple:

# The decorated function returns a Response object,
# which has a status_code attribute. 200 means
# success; 500 indicates a server-side error.

def retry(func):
    def retried_func(*args, **kwargs):
        MAX_TRIES = 3
        tries = 0
        while True:
            resp = func(*args, **kwargs)
            if resp.status_code == 500 and tries < MAX_TRIES:
                tries += 1
                continue
            break
        return resp
    return retried_func


This gives you an easy-to-use @retry decorator:

@retry

def make_api_call():

    # ....

It may not be so easy to start writing decorations. Although it's not as difficult as building rockets, you also need to spend some time learning and mastering the secrets. Most programs can be mastered. When you become a team player who can write the decorators well and solve real problems, other developers will use the decorators you have developed. Because once the most difficult part, that is, the implementation of the decorator is completed, it is very easy to use the decorator. This can greatly magnify the positive impact of the code you write, which will make you a team hero. Let your career take off

I've trained hundreds of software engineers to use Python more efficiently, and these teams consistently reflect that decorators are the most valuable and the most important tool they use in Python high-level programming. That's why it became the focus of the next Python course: after the basic online course on May 25 and 26, 2016.

No matter how you learn to implement decoration, you will be excited about what you can do. There is no doubt that it can permanently change the way you use Python.

Keywords: Python JSON Attribute Programming

Added by willfitch on Wed, 26 Jun 2019 21:20:29 +0300