同步阅读进度,多语言翻译,过滤屏幕蓝光,评论分享,更多完整功能,更好读书体验,试试 阅读 ‧ 电子书库
8.3 函数式,函数的,函数化
Python包括很多针对函数式编程的工具。这些内置的函数涵盖了以下这些基本部分。
Python 3中map的用法
>>> map(lambda x: x + "bzz!", ["I think", "I'm good"])<map object at 0x7fe7101abdd0>
>>> list(map(lambda x: x + "bzz!", ["I think", "I'm good"]))
['I thinkbzz!', "I'm goodbzz!"]
示例 8.2 Python 3 中filter的用法
>>> filter(lambda x: x.startswith("I "), ["I think", "I'm good"])<filter object at 0x0d636dd0>
>>> list(filter(lambda x: x.startswith("I "), ["I think", "I'm good"]))
['I think']
提示
可以使用生成器和列表解析实现与filter或者map等价的函数。使用列表解析实现map>> (x + "bzz!" for x in ["I think", "I'm good"])<generator object <genexpr> at 0x0d697dc0>
>> [x + "bzz!" for x in ["I think", "I'm good"]]
['I thinkbzz!', "I'm goodbzz!"]使用列表解析实现filter>> (x for x in ["I think", "I'm good"] if x.startswith("I "))
<generator object <genexpr> at 0x0d697dc0>
>> [x for x in ["I think", "I'm good"] if x.startswith("I ")]
['I think']在Python 2中这样使用生成器会返回一个可迭代的对象而不是列表,就像Python 3中的map和filter函数。
while i < len(mylist):
print("Item %d: %s" % (i, mylist[i]))
i += 1
可以用这样的写法:
for i, item in enumerate(mylist):print("Item %d: %s" % (i, item))
for x in iterable:
if not x:
return False
return True
def any(iterable):
for x in iterable:
if x:
return True
return False
这两个函数对于判断这个可迭代对象中的任何或所有值是否满足给定的条件非常有用:
mylist = [0, 1, 3, -1]if all(map(lambda x: x > 0, mylist)):
print("All items are greater than 0")
if any(map(lambda x: x > 0, mylist)):
print("At least one item is greater than 0")
>>> map(len, keys)
<map object at 0x7fc1686100d0>
>>> zip(keys, map(len, keys))
<zip object at 0x7fc16860d440>
>>> list(zip(keys, map(len, keys)))
[('foobar', 6), ('barzz', 5), ('ba!', 3)]
>>> dict(zip(keys, map(len, keys)))
{'foobar': 6, 'barzz': 5, 'ba!': 3}
到这里你可能已经注意到Python 2和Python 3的返回类型的不同。在Python 2中大多数Python内置的纯函数会返回列表而不是可迭代的对象,这使得它们的内存利用没有Python 3.x中那么高效。如果正计划使用这些函数编写代码,记住在Python 3中才能最大的发挥它们的作用。如果你仍然在使用Python 2,也不用气馁,标准库中的itertools模块提供了许多这些函数的迭代器版本(itertools.izip、itertoolz.imap、itertools.ifilter等)。
然而,在上面这个列表中仍然缺少一个重要的工具。处理列表时,一个常见的任务就是在从列表中找出第一个满足条件的元素。这通常可以用函数这么实现:
def first_positive_number(numbers):for n in numbers:
if n > 0:
return n
也可以写一个函数式风格的:
def first(predicate, items):for item in items:
if predicate(item):
return item
first(lambda x: x > 0, [-1, 0, 1, 2])
或者更精确一点儿:
# Less efficientlist(filter(lambda x: x > 0, [-1, 0, 1, 2]))[0] ①
# Efficient but for Python 3
next(filter(lambda x: x > 0, [-1, 0, 1, 2]))
# Efficient but for Python 2
next(itertools.ifilter(lambda x: x > 0, [-1, 0, 1, 2]))
① 注意,如果没有元素满足条件,可能会抛出IndexError,促使list(filter())返回空列表。
为了避免在每一个程序中都写同样的函数,可以包含这个小巧且使用的Python包first(https://pypi.python.org/pypi/first)。
示例 8.3 使用first
>>> first([0, False, None, [], (), 42])
42
>>> first([-1, 0, 1, 2])
-1
>>> first([-1, 0, 1, 2], key=lambda x: x > 0)
1
参数key可以用来指定一个函数,接收每个元素作为参数并返回布尔值指示该元素是否满足条件。
你可能已经注意到,在本章相当一部分示例中我们使用了lambda表达式。lambda最早在Python中引入实际是为了给函数式编程函数提供便利,如map()和filter(),否则每次想要检查不同的条件时将需要重写一个完整的新函数:
import operatorfrom first import first
def greater_than_zero(number):
return number > 0
first([-1, 0, 1, 2], key=greater_than_zero)
这段代码和前面的例子功能相同,但更加难以处理:如果想要获得序列中第一个大于42的数,则需要定义一个合适的函数而不是以内联(in-line)的方式在first中调用。
尽管lambda在帮助我们避免此类问题时是有用的,但它仍然是有问题的。首先也最明显的,如果需要超过一行代码则不能通过lambda传入key函数。在这种场景下,则又需要返回繁复的模式,为每个需要的key编写一个新的函数定义。我们真的需要这样做吗?
functools.partial是以更为灵活的方案替换lambda的第一步。它允许通过一种反转的方式创建一个包装器函数:它修改收到的参数而不是修改函数的行为。
from functools import partialfrom first import first
def greater_than(number, min=0):
return number > min
first([-1, 0, 1, 2], key=partial(greater_than, min=42))
默认情况下,新的greater_than函数功能和之前的greater_than_zero函数类似,但是可以指定要比较的值。在这个例子中,我们向functools.partial传入我们的函数和期望的最小值min,就可以得到一个min设定为42的新函数。换句话说,可以写一个函数并利用functools.partial根据需要针对给定的某个场景进行自定义。
在这个例子中,尽管需求严格限定但还是需要两行代码。例子中需要做的只是比较两个数,假如Python中有内置的这种比较函数呢?事实证明,operator模块就是我们要找的。
import operatorfrom functools import partial
from first import first
first([-1, 0, 1, 2], key=partial(operator.le, 0))
这里我们看到,functools.partial也支持位置参数。在这个例子中,operator.le(a, b)接收两个数并返回第一个数是否小于等于第二个数。这里向functools.partial传入的0会被赋值给a,向由functools.partial返回的函数传递的参数会被赋值给b。所以它运行起来和最初的例子完全一样,不需要使用lambda或其他额外的函数。
注意
functools.partial在替换lambda时是很有用的,而且通常被认为是更好的选择。在Python语言中lambda被看做是一种非常规方式,因为它将函数体限定为一行表达式2。另一方面,functools.partial可以围绕原函数进行很好的封装。Python标准库中的itertools模块也提供了一组非常有用的函数,也很有必要记住。因为尽管Python本身提供了这些函数,但我还是看到很多程序员试图实现自己的版本。
这些函数在和operator模块组合在一起时特别有用。当一起使用时,itertools和operator能够覆盖通常程序员依赖lambda表达式的大部分场景,如示例8.4所示。
示例 8.4 结合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 0xb000d0>), ('baz', <itertools.
_grouper object at 0xb00110>)]
>>> [(key, list(group)) for key, group in list(itertools.groupby(a, operator.
itemgetter('foo')))]
[('bar', []), ('baz', [{'y': 43, 'foo': 'baz'}])]
在这个例子中,也可以写成lambda x: x['foo'],但使用operator可以完全避免使用lambda。
1技术上可以,但那不是期望的方式。2曾经计划在 Python 3中移除,但是最终没有。
请支持我们,让我们可以支付服务器费用。
使用微信支付打赏