Python condensed notes (5)-functional programming

Python condensed notes (5)-functional programming

Functional programming is a programming paradigm with a high degree of abstraction. A function written in a purely functional programming language has no variables. Therefore, for any function, as long as the input is certain, the output is certain. This kind of pure function is called For no side effects. And the programming language that allows the use of variables, because the variable state inside the function is uncertain, the same input may get different outputs, so this kind of function has side effects.

One feature of functional programming is that it allows the function itself to be passed into another function as a parameter, and it is also allowed to return a function!

Python provides partial support for functional programming. Since Python allows the use of variables, Python is not a purely functional programming language.

Return function

The function can return the function as the return value. If you do not need summary immediately, but in the code behind, instead of returning the result of the sum, it returns the function of the sum:

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum
 

When lazy_sum() is called, what is returned is not the sum result, but the sum function:

>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>
 

When the function f is called, the result of the sum is actually calculated:

>>> f()
25
 

The above form is called a closure.

When called lazy_sum(), each call will return a new function, even if the same parameters are passed in:

>>> f1 = lazy_sum(1, 3, 5, 7, 9)
>>> f2 = lazy_sum(1, 3, 5, 7, 9)
>>> f1==f2
False
 

f1()And f2()the result of the call independently of each other.

Closure

The returned function references local variables in its definition args. Therefore, when a function returns a function, its internal local variables are also referenced by the new function.

Closures are simple to use, but not easy to implement.

Wrong demonstration


def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs

f1, f2, f3 = count()

 

You might think that the result of calling f1(), f2() and f3() should be 1, 4, 9, but the actual result is:

>>> f1()
9
>>> f2()
9
>>> f3()
9
 

All are 9! The reason is that the returned function refers to the variable i, but it is not executed immediately. When all three functions return, the variable i they refer to has become 3, so the final result is 9.

One thing to keep in mind when returning a closure is: the return function does not reference any loop variables, or variables that will change later.

What if the loop variable must be referenced? The method is to create another function and bind the current value of the loop variable with the parameter of the function. No matter how the loop variable is subsequently changed, the value bound to the function parameter remains unchanged:

def count():
    def f(j):
        def g():
            return j*j
        return g
    fs = []
    for i in range(1, 4):
        fs.append(f(i)) # f(i) i f()
    return fs
 

Anonymous function lambda

When passing in a function, sometimes it is more convenient to pass in an anonymous function directly.

The anonymous function is lambda x: x * xactually:

def f(x):
  return x * x

 

The keyword lambdarepresents an anonymous function, and the x before the colon represents the function parameter.

Anonymous functions have a limitation, that is, there can only be one expression, without writing return, the return value is the result of the expression.

There is an advantage to using anonymous functions, because functions have no names, so you don't have to worry about function name conflicts. In addition, an anonymous function is also a function object. You can also assign an anonymous function to a variable, and then use the variable to call the function:

>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x101c6ef28>
>>> f(5)
25
 

Similarly, anonymous functions can also be returned as return values, such as:

def build(x, y):
  return lambda: x * x + y * y
 

Decorator

A function is also an object, and the function object can be assigned to a variable, so the function can also be called through the variable.

>>> def now():
... print('2015-3-25')
...
>>> f = now
>>> f()
2015-3-25
 

The function object has an __name__attribute, you can get the name of the function:

>>> now.__name__
'now'
>>> f.__name__
'now'
 

Suppose we want to enhance the now()function of the function, for example, automatically print the log before and after the function is called, but do not want to modify the definition of the now() function. This way of dynamically adding functions during the running of the code is called a "decorator" ( Decorator).

DecoratorIt is a higher-order function that returns a function. It can be defined as follows:

def log(func):
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

 

Observe the above log, because it is one decorator, it accepts a function as a parameter and returns a function. We need to use Python's @syntax to decoratorplace it in the definition of the function:

@log
def now():
    print('2015-3-25')
 

Calling a now()function not only runs the now()function itself, but also now()prints a line of log before running the function:

>>> now()
call now():
2015-3-25
 

Put @loginto now()the definition of the function, equivalent to the implementation of the statement:

now = log(now)
 

If the decorator itself needs to pass in parameters, then you need to write a higher-order function that returns the decorator, which will be more complicated to write. For example, to customize the log text:

def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator
 

The usage of this 3-level nested decorator is as follows:

@log('execute')
def now():
    print('2015-3-25')
 

Equivalent to

now = log('execute')(now)
 

The decorator will change the function __name__attribute. Python's built-in functools.wrapscan solve this problem, just remember to wrapper()add it before the definition @functools.wraps(func).

import functools

def log(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator
 

Partial function

When the number of parameters of a function is too large and needs to be simplified, functools.partiala new function can be created using this new function, which can fix some of the parameters of the original function, making it easier to call.

For example int()the function can be converted to an integer string, only when an incoming string, int()default function decimal conversion, if the incoming base parameter can be N-ary conversion:

>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565

 

To convert a large number of binary strings, it is int(x, base=2)very troublesome to pass in each time to simplify the code

>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85
 

When creating a partial function, you can actually receive the function object *argsand **kwthese 3 parameters

int2 = functools.partial(int, base=2)
int2('10010')
 

Equivalent to

kw = { 'base': 2 }
int('10010', **kw)
 

When incoming:

max2 = functools.partial(max, 10)
 

In fact, 10 is automatically added to the left as part of *args, that is:

max2(5, 6, 7)
 

Is equivalent to:

args = (10, 5, 6, 7)
max(*args)