This is the 10th day of my participation in the August More text Challenge. For details, see: August More Text Challenge

Python standard library includes many powerful module, can make your code is clean and efficient, today we introduce functools module, this module provides a number of useful higher-order functions, we can use these higher-order functions to implement caching function, overloading, and create a decorator, but our code more efficient, well, Let’s take a look at what the FuncTools module provides.

The cache

The functools module provides a simple but powerful cache function (decorator) called lru_cache(). Lru_cache () can be applied directly to our functions in the form of a syntactic sugar @ to provide caching for our functions, following the least recently used rule and caching the results of function execution. It is convenient to query before the next function execution. If the parameters of the function are fixed and the logic is fixed, it is very necessary to effectively reduce the execution time of the function.

import functools
import time


@functools.lru_cache(maxsize=32)
def add(a, b) :
    print("sleep 2s")
    time.sleep(2)
    return a+b


print("The first call:", add(4.5))  # First call, no cache, execute the original function, results added to the cache
print("Second call:", add(4.5))  The second call, with the same arguments as the first, matches the cache and returns to the cache without executing the original function
print("The third call:", add(5.5))  # The third call, the parameter is different from before, no cache, execute the original function, the result is added to the cache
print(add.cache_info())  # Check the cache information of the function, which shows the number of cache hits and misses
Copy the code

The execution result is:

Sleep 2 s first call: 9 second call: 9 sleep 2 s third call:  10 CacheInfo(hits=1, misses=2, maxsize=32, currsize=2) Process finished with exit code 0Copy the code

In this example, we use the @lru_cache decorator to handle the add() function and cache the results of execution (up to 32 results can be cached). To see if the cache really works, we use the cache_info() method to check the cache information of the function, which shows the number of cache hits and misses. The result shows that there is no cache on the first call, add() executes in the cache, and hits the cache on the second call. Failed to hit the cache on the third attempt.

If you want a more granular cache then you can also include the optional typed=true argument which allows different types of arguments to be cached separately. For example, add(4.0, 5.0) and add(4, 5) will be treated as different calls.

In fact, after Python3.8, there is another decorator that can be used for caching called cached_Property. This function is used to cache the results of class properties. If your class properties are immutable, you can use this.

To compare

You probably already know, you can use __lt__ (), __gt__ () and __eq__ () in Python implementation comparison operators, such as <, > or = =. But the implementation __lt__ (), __gt__ (), __eq__ (), __le__ (), __ge__ () can be quite tedious, just functools module contains total_ordering ‘decorators, it can help us to do this. We only need to implement __eq__ (), and __lt__ (), __gt__ (), __le__ (), __ge__ () in a way, it will help us to provide other:

from functools import total_ordering


@total_ordering
class MyNumber:
    def __init__(self, value) :
        self.value = value

    def __gt__(self, other) :
        return self.value > other.value

    def __eq__(self, other) :
        return self.value == other.value

print(MyNumber(5) > MyNumber(3))
# True
print(MyNumber(1) < MyNumber(5))
# True
print(MyNumber(5) >= MyNumber(5))
# True
print(MyNumber(5) <= MyNumber(2))
# False
Copy the code

Obviously, it helps to reduce code and improve readability.

Partial function

The partial function in the functools module is used to fix the parameters of a function (that is, set the default values) and return a new function that is easier to call than the original function. If that sounds confusing, let’s look at some practical examples. In Python, the int() function converts a numeric string to an integer. The default is to convert to decimal:

print(int(1234))
# 1234
Copy the code

But the int() function also provides a base argument, which defaults to 10. If the base argument is passed, then the conversion can be done to the N base. That is, the default numeric string is in base N. To convert it to the 10 base, for example:

print(int("1234", base=8))
# Default 1234 digits in base 8, convert to base 10:668
print(int("1234", base=16))
# Default 1234 hexadecimal digits, convert to decimal: 4660
Copy the code

Int (x, base=8); int(x, base=8);

def int8(x, base=8) :
    return int(x, base)

print(int8("1234"))
Copy the code

In this way, it’s very convenient.

Functools.partial creates a partial function. Instead of defining int8(), we can create a new function int8 using the following code:

int2 = functools.partial(int, base=8)
print(int2('1234'))
Copy the code

So in a nutshell, functools.partial is a function that fixes some of its arguments (that is, sets defaults), returns a new function, and makes it easier to call the new function.

overloading

We all know that function overloading is technically impossible in Python, but there is actually a simple way to do something similar, using the SingleDispatch function decorator in the FuncTools module. Here’s an example:

@singledispatch
def show(obj) :
    print(obj, type(obj), "obj")


@show.register(str)
def _(text) :
    print(text, type(text), "str")


@show.register(int)
def _(n) :
    print(n, type(n), "int")

    
show(1234)
show("abcd")
show({"abcd": 1234})
Copy the code

The result is:

1234 <class 'int'> int
abcd <class 'str'> str
{'abcd': 1234} <class 'dict'> obj

Process finished with exit code 0
Copy the code

If you pass a different type argument to show(), you call a different function and behave differently. In fact, singleDispatch implements a single generic function by adding a.register method to the function, which supports binding a variable type to a function. Then it returns an overloaded function. The simultaneously bound function is called when the input value is of a type bound via.register.

wraps

This part was mentioned in my previous post, but for the more interested, check out the built-in decorator @functools.wrap

conclusion

The FuncTools module provides a number of useful features and decorators that can help you build better code, and I’ve only mentioned some of them here. Functools — Higher-Order Functions and Operations on Callable Objects

Finally, I would like to thank my girlfriend for her tolerance, understanding and support in work and life.