第四章-函数式编程

函数式编程(Functional Programming), 可以归结为面向过程的程序设计, 但是其思想更加接近于数学计算

函数有副作用: 对于同一个函数相同的输入, 产生的结果是不一样的

函数无副作用: 上述的前提下函数输出是一样的

函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量

函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数

1 高阶函数

  变量可以指向函数, 也就是函数本身也可以复制给变量

  实际上函数名本身也是变量

  将函数作为参数传给另一个函数, 这样的函数就是高阶函数

1.1 map和reduce

  map有两个参数, 一个是函数, 另一个是迭代器

>>> def f(x):
...     return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]

  reduce把一个函数作用在一个序列上, 这个函数必须接受两个参数,  reduce会先把序列的前两个参数传入函数计算,返回的结果会继续和下一个参数继续进行运算,直到完成整个序列的运算

>>> from functools import reduce
>>> def add(x, y):
...     return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25

  其效果等同于1+3+5+7+9

>>> from functools import reduce
>>> def fn(x, y):
...     return x * 10 + y
...
>>> def char2num(s):
...     return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]
...
>>> reduce(fn, map(char2num, '13579'))
13579

  该程序可以整个为

from functools import reduce

def str2int(s):
    def fn(x, y):
        return x * 10 + y
    def char2num(s):
        return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]
    return reduce(fn, map(char2num, s))

  最后还可以用lambda函数优化

from functools import reduce

def char2num(s):
    return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]

def str2int(s):
    return reduce(lambda x, y: x * 10 + y, map(char2num, s))

1.2 filter

  filter用于过滤, 会传入两个参数, 一个是函数, 一个是序列. 函数只会处理一个参数且函数返回值是True或者False.当返回值是True的时候该值保留下来, 当返回值是False的时候该参数将不会被保留.

def not_empty(s):
    return s and s.strip()

list(filter(not_empty, ['A', '', 'B', None, 'C', '  ']))
# 结果: ['A', 'B', 'C']

1.3 sorted

  sorted是用于排序的.

  当排序的序列中是数字的时候, 直接对数字进行排序. 当序列中的内容是非数字的时候排序就不能以大小判断了. 此时可以传入一个参数key来限定排序方法.

>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]

  默认情况下, 字母的排序是按照ASCII的排序关系排序

  要进行反向排序, 需要加入第三个参数, reverse=True

>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']

2 返回函数

  函数可以做为对象赋值给变量, 因而返回函数实际上就是将函数封装传出去了

  例如想要做一个求和的函数, 但是并不希望调用该函数执行之后就返回结果, 而是在想要使用的时候调用.

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

  可以看到 lazy_sum()函数返回的就是sum函数.当返回值执行函数的时候才进行计算

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

3 闭包

  闭包(Closure)就是传出返回函数, 传出的函数在执行的时候可以调用原函数内容的变量. 类似于上述lazy_sun的情况.

  闭包的基本形式是:

    在函数F1中, 定义F2, F2只能引用F1定义的变量, 之后F1函数返回F2的函数名字

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

f1, f2, f3 = count()

  此时关键来了, 当执行f1, f2, f3的时候返回的结果是..

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

  原因是由于闭包的特性是在具体调用的时候去取原函数中变量的值. 然而在返回fs的时候会走count()中的代码, 在具体函数执行之前i值已经是i=3了.

  因此在闭包的时候, 不能引用任何循环变量.

  如果真的需要引用这个循环变量的话, 就需要将循环变量绑定到一个函数的参数当中.

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

  具体还可以通过lambda表达式优化上述代码

def count():
    def f(j):
        def g():
            return j*j
        return g
    return [f(i) for i in range(1,4)]

f1, f2, f3 = count()

4 匿名函数

  匿名函数用于一些操作不必要比较短小没必要写成函数的方式

>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]

  匿名函数只能是一个表达式, 且不用写return, 返回值就是表达式的结果.

  常用的匿名函数用lambda表达式.

  写法:

    lambda 参数 : 参数的运算式  (返回的结果是参数的表达式的结果)

    lambda 参数表达式  (返回的值就是表达式的计算结果)

5 装饰器

  装饰器(Decorator), 在不修改原函数的情况下, 在代码运行期间动态增加功能的方式

def log(func):
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper
@log
def now():
    print('2017-3-28')

>>> now()
call now():
2017-3-28

  此处的@log相当于执行了

now = log(now)

  另外定义装饰器的时候, 可以用 @装饰器(参数) 装饰器上添加参数传入, 对此原有的装饰器需要在外层再加一层函数来传入参数, 因为如果只在刚才的最外层添加是无法做到准确传入传入的参数的

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

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

  此处的@log('execute')相当于执行了

now = log('execute')(now)

  但是这样的装饰器还是有一个问题就是:

    每个函数对象都有一个__name__属性, 这个属性保存了函数的名字. 但是执行完装饰器之后, 该属性值__name__会被覆盖成别的名字.改进方法是:

import functools

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

    对于带参数的装饰器有:

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

6 偏函数

  偏函数是 functools.partial模块里, 通过固定某个函数的某个参数来达到形成新函数简化操作的目的.

  通过functools.partial可以传入原来的函数名字, 设定的默认参数的值, 来返回一个新的函数更加方便的定义并且使用

  诸如:

    int(要转化的参数, base进制)

    当我们要定义直接转化二进制的时候可以

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

  具体的

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

  

人若有恒 无所不成
原文地址:https://www.cnblogs.com/weihuchao/p/6628413.html