Python函数式编程

函数式编程

在以函数式风格写代码时,函数应该设计成没有其他副作用。也就是说,函数接收参数并生成输出而不保留任何状态或修改任何不反映在返回值中的内容。遵循这种理想方式的函数可以被看成纯函数式函数

举例:

一个非纯函数:

def remove_list(mylist):
    mylist.pop(-1) #修改了参数mylist。使用者在不看代码源码的情况下,并不知道有被修改的副作用。

一个纯函数:

def butlast(mylist):
    return mylist[:-1]

函数式编程具有以下实用的特点。

  • 可形式化证明。
  • 模块化。模块化编码能够在一定程度上强制对问题进行分治解决并简化在其他场景下的重用。
  • 简洁。 函数式编程通常比其他范型更为简洁。
  • 并发。 纯函数式函数是线程安全的并且可以并行运行。
  • 可测性。测试一个函数式程序是非常简单的:所有需要做的仅仅是一组输入和一组期望的输出。而且是幂等的。

生成器

生成器适合运行时计算。从而不用占用那么多内存。
生成器(generator)是这样一种对象:在每次调用它的next()方法时返回一个值(yield返回的),直到它抛出StopIteration。

要创建一个生成器所需要做的只是写一个普通的包含yield语句的Python函数。Python会检测对yield的使用并将这个函数标识为一个生成器。当函数执行到yield语句时,它会像return语句那样返回一个值,但一个明显不同的在于:解释器会保存对栈的引用,它将被用来在下一次调用next函数时恢复函数的执行。

创建一个生成器:

def mygenerator():
    yield 1
    yield 2
    yield 'a'

可以通过inspect.isgeneratorfunction来检查一个函数是否是生成器。

def isgeneratorfunction(object):
    return bool((isfunction(object) or ismethod(object)) and object.func_code.co_flags & CO_GENERATOR)

Python3中提供了另一个有用的函数inspect.getgeneratorstate
看例子可以看到它的作用。

>>> import inspect
>>> def mygenerator():
...     yield 1
... 
>>> gen = mygenerator()
>>> gen
<generator object mygenerator at 0x104f2e468>
>>> inspect.getgeneratorstate(gen)
'GEN_CREATED'
>>> next(gen)
1
>>> inspect.getgeneratorstate(gen)
'GEN_SUSPENDED'
>>> next(gen)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  StopIteration
  >>> inspect.getgeneratorstate(gen)
  'GEN_CLOSED'
  >>> 

在Python中,生成器的构建是通过当函数产生某对象时保持一个地栈的引用来实现的,并在需要时恢复这个栈,例如,当调用next()时会再次执行。

yield还有一个不太常用的功能:它可以像函数调用一样返回值。这允许通过调用它的send()函数来向生成器传入一个值。

示例:通过yield返回值:

def h():
    print('Wen Chuan')
    m = yield 5
    print(m)
    d = yield 12
    print('We are together!')
                    
c = h()
r = next(c)
print("call 1",r)
r = c.send('Fighting!')
print("call 2",r)

输出结果:

Wen Chuan
call 1 5
Fighting!
call 2 12

从上例可看出。next和send的返回值是yield右边的表达式。
而send 影响的是yield的返回值,即左值。
第一次执行next()时,执行到yield 5, 5即next()的返回值。
再执行send(‘Fighting’)时,执行 m = ‘Fighting’

PEP 289引入了生成器表达式。通过使用类似列表解析的语法可以构建单行生成器。

>>> (x.upper() for x in ['hello','world'])
<generator object <genexpr> at 0x10aefddb0>
>>> gen = (x.upper() for x in ['hello','world'])
>>> list(gen)
['HELLO', 'WORLD']
>>> 

列表解析

列表解析(list comprehension, 简称listcomp)让你可以通过声明在单行内构造列表的内容。
在Python用列表解析比用for循环用的多。

>>> [pow(i,2) for i in (1,2,3)]
[1, 4, 9]

列表解析里可以加if过滤

>>> w(i,2) for i in (1,2,3) if i>=2]
[4, 9]

列表解析可以嵌套

>>> x = [word.capitalize()
... for line in ("hello world?", "world!", "or not")
... for word in line.split()
... if not word.startswith("or")]
>>> x
['Hello', 'World?', 'World!', 'Not']
>>> 

函数式,函数的,函数化

Python包括很多会对函数式编程的工具。这些内置的函数涵盖了以下这些基本部分。

  • map(function, iterable) 对iterable中的每一个元素应用function,并在Py2中返回一个列表,或者在py3中返回可迭代的map对象。
  • filter(function or None, iterable)地iterable中的元素应用function对返回结果进行过滤,并在py2中返回一个列表,或者在py3中返回可迭代的filter对象。
  • enumerate(iterable[,start]) 返回一个可迭代的enumerate对象,它生成一个元组序列,每个元组包括一个整形索引(如果提供了的话,则从start开始)和iterable中对应的元素。当需要参考数组的索引编写代码时这是很有用的。
  • sorted(iterable, key=None, reverse=False)返回iterable的一个已排序版本。通过参数key可以提供一个返回要排序的值的函数。
  • any(iterable)和all(iterable) 都返回一个依赖于iterable返回的值的布尔值。any有一个为真则为值。all,全为值 则为真。
  • zip(iter1[, iter2 […]])接收多个序列并将它们组合成元组。它在将一组键和一组值组合成字典时很有用。

在py2中是返回列表,而不是可迭代对象,从而在内存上面的利用不那么高效。要想也像py3一样返回可迭代对象,可以使用标准库的itertools模块,其提供了许多这些函数的迭代器版本(itertools.izip、itertools.imap、itertools.ifilter等)

有个first模块提供了从一个列表中选择首个满足条件的项。

>>> from first import first
>>> first([0,False,None,[],42])
42
>>> first([-1,0,1,2], key=lambda x: x>0)
1

lambda在单行函数时很在效,可以避免定义单行函数,而直接以内联的方式使用。
但在超过一行的函数时,则不管用了。

functools.partial是以更为灵活的方案替代lambda的第一步。它允许通过一种反转的方式创建一个包装器函数:它修改收到的参数而不是修改函数的行为。

from functools import partial
from first import first

def greater_than(number, min=0):
    return number > min
    
first([-1, 0, 1, 2], key=partial(greater_than, min=42))

Python标准库中的itertools模块也提供了一组非常有用的函数,也很有必要记住。

  • chain(*iterables)依次迭代多个iterables但并不会构造包含所有元素的中间列表。
  • combinations(iterable, r)从给定的iterable中生成所有长度为r的组合。
  • compress(data, selectors)对data应用来自selectors的布尔掩码并从data中返回selectors中对应为真的元素。
  • count(start, step)创建一个无限的值的序列,从start开始,步长为step。
  • cycle(iterable)重复的遍历iterable中的值。
  • dropwhile(predicate, iterable)过滤iterable中的元素,丢弃符合predicate描述的那些元素。
  • groupby(iterable, keyfunc)根据keyfunc函数返回的结果对元素进行分组并返回一个迭代器。
  • permutations(iterable[, r])返回iterable中r个元素的所有组合。
  • product(*iterables)返回iterables的笛卡尔积的可迭代对象,但不使用嵌套的for循环。
  • takewhile(predicate, iterable)返回满足predicate条件的iterable中的元素。

这些函数在和operator模块组合在一起时特别有用。当一起使用时,itertools和operator能够覆盖通常程序员依赖lambda表达式的大部分场景。

示例 结合itertools.groupby使用operator模块

>>> import itertools
>>> a = [{'foo':'bar'},{'foo':'bar','x':42},{'foo':'baz','y':43}]
>>> import operator
>>> list(itertools.groupby(a, operator.itemgetter('foo')))
[('bar', <itertools._grouper object at 0x10af13f28>), ('baz', <itertools._grouper object at 0x10af27198>)]
>>> [(key,list(group)) for key, group in list(itertools.groupby(a, operator.itemgetter('foo')))]
[('bar', []), ('baz', [{'y': 43, 'foo': 'baz'}])]
>>> 
Tags: