“This is the sixth day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

Function decorator

Function decorators are used to “mark” functions in source code to enhance their behavior in some way.

A decorator is a callable object whose argument is another function (the function being decorated). A decorator might process the decorated function and then return it, or replace it with another function or callable.

Here’s an example:

@decorate
def target() :
	print('running target()')

# is equivalent to
def target() :
	print('running target()')

target = decorate(target)
Copy the code

One of the great features of decorators is the ability to replace decorated functions with other functions. The second feature is that they run immediately after the decorated function definition; This is usually done at import time (that is, when Python loads modules).

Decorators are typically defined in one module and then applied to functions in other modules.

Most decorators define a function internally and then return it.

Decorators in the standard library

Python has three built-in functions for decorating methods: Property, classMethod, and StaticMethod.

Another common decorator is functools.wraps, which is used to help construct well-behaved wraps. Two of the most noteworthy decorators in the standard library are lru_cache and Singledispatch (new in Python 3.4). Both decorators are defined in the FuncTools module.

functools.lru_cache

Functools. lru_cache implements memoization function. It saves the results of time-consuming functions to avoid double-counting when the same arguments are passed in. LRU is short for “Least Recently Used”. It indicates that the cache does not grow indefinitely and items that have not been Used for a period of time are thrown away.

Lru_cache can be configured using two optional parameters. It was signed:

functools.lru_cache(maxsize=128, typed=False)
Copy the code

The maxsize parameter specifies how many calls to store. When the cache is full, the old results are thrown away to make room. For best performance, maxsize should be set to a power of two. The typed parameter, if set to True, stores results from different argument types separately. That is, it separates floating point numbers that are normally considered equal from integer arguments such as 1 and 1.0.

Because Lru_cache uses a dictionary to store results, and the keys are created based on location and keyword arguments passed in on the call, all arguments to a function decorated by lru_cache must be hashed.

functools.singledispatch

Because Python does not support overloaded methods or functions, we cannot use different signatures to define variations of functions with the same function name, nor can we handle different data types in different ways. In Python, a common practice is to use a string of if/elif/elif to call specialized functions such as functionA_str, functionA_int, and so on. This is inconvenient for user extension of modules and unwieldy: over time, the dispatch function functionA becomes large, and it is tightly coupled to specialized functions.

The FuncTools. singleDispatch decorator can split the whole scheme into modules and even provide special functions for classes that you can’t modify. An ordinary function decorated with @SingleDispatch becomes a generic function: a set of functions that perform the same operation in different ways, depending on the type of the first argument.

Specialized functions can be registered anywhere in the system and in any module.

Here’s an example:

from functools import singledispatch
import numbers
import html

@singledispatch				# flags base functions that handle type object
def htmlize(obj) :
    content = html.escape(repr(obj))
    return '<pre>{}</pre>'.format(content)

@htmlize.register(str)		Each specialized function is decorated with @«base_function».register(«type»).
def _(text) :			The name of the specialized function does not matter; _ is a good choice
    content = html.escape(text).replace('\n'.'<br>\n')
    return '<p>{0}</p>'.format(content)

@htmlize.register(numbers.Integral)
def _(n) :
    return '<pre>{0} (0x{0:x})</pre>'.format(n)
Copy the code

Parameterize decorators

Make the decorator accept additional parameters: create a decorator factory function, pass the parameters to it, return a decorator, and then apply it to the function to be decorated.

Here’s an example:

registry = set(a)def register(active=True) :		# accept an optional keyword argument
    def decorate(func) :			# Real decorators
        print('running register(active=%s)->decorate(%s)' % (active, func))
        
        if active:		Func is registered only if the value of the active parameter (retrieved from the closure) is True
            registry.add(func)
        else:			If active is not true and func is in Registry, delete it
            registry.discard(func)
        return func		# decorate is a decorator and must return a function
    
    return decorate

@register(active=False)	The # @register factory function must be called as a function, passing in the required parameters
def f1() :
	print('running f1()')
    
@register()		Register must be called as a function even if no arguments are passed in, that is, to return the real decorator
def f2() :
	print('running f2()')
Copy the code