42 KiB
Python 的 map():不使用循环处理可重复项
*立即观看**本教程有真实 Python 团队创建的相关视频课程。和文字教程一起看,加深理解: Python 的 map()函数:变换 Iterables
Python 的 map() 是一个内置函数,允许你处理和转换一个 iterable 中的所有项,而不需要使用显式的 for循环,这种技术通常被称为映射。当您需要将一个转换函数应用到一个可迭代对象中的每一项,并将它们转换成一个新的可迭代对象时,map()非常有用。map()是 Python 中支持函数式编程风格的工具之一。
在本教程中,您将学习:
- Python 的
map()是如何工作的 - 如何使用
map()将转换成不同类型的 Python 可迭代对象 - 如何将**
map()与其他功能工具结合起来进行更复杂的变换** *** 你能用什么工具来取代map()并让你的代码更python 化*
*有了这些知识,你将能够在你的程序中有效地使用map(),或者,使用列表理解或生成器表达式来使你的代码更具 Pythonic 化和可读性。
为了更好地理解map(),一些关于如何使用可迭代、for循环、函数和 lambda函数的知识会有所帮助。
免费奖励: 掌握 Python 的 5 个想法,这是一个面向 Python 开发者的免费课程,向您展示将 Python 技能提升到下一个水平所需的路线图和心态。
Python 中的函数式编码
在 函数式编程 中,计算是通过组合函数来完成的,这些函数接受参数并返回一个(或多个)具体值作为结果。这些函数不修改它们的输入参数,也不改变程序的状态。它们只是提供给定计算的结果。这几种函数俗称纯函数。
理论上,使用函数式风格构建的程序更容易:
函数式编程通常使用列表、数组和其他可重复项来表示数据,以及一组对数据进行操作和转换的函数。当使用函数式风格处理数据时,至少有三种常用的技术:
-
映射 包括将一个转换函数应用于一个可迭代对象以产生一个新的可迭代对象。新 iterable 中的项目是通过对原始 iterable 中的每个项目调用转换函数来生成的。
-
过滤 包括对一个可迭代对象应用一个谓词或布尔值函数来生成一个新的可迭代对象。新 iterable 中的项是通过过滤掉原始 iterable 中使谓词函数返回 false 的任何项而产生的。
-
归约 包括将归约函数应用于一个迭代项,以产生一个单一的累积值。
根据吉多·范·罗苏姆的说法,与函数式语言相比,Python 受命令式编程语言的影响更大:
我从来不认为 Python 会受到函数式语言的严重影响,不管人们怎么说或怎么想。我更熟悉 C 和 Algol 68 等命令式语言,尽管我已经将函数作为一级对象,但我并不认为 Python 是一种函数式编程语言。(来源)
然而,回到 1993 年,Python 社区需要一些函数式编程特性。他们要求:
- 匿名函数
- 一个
map()功能 - 一个
filter()功能 - 一个
reduce()功能
由于社区成员的贡献,这些功能特性被添加到语言中。如今, map() 、 filter() 、 reduce() 是 Python 中函数式编程风格的基本组成部分。
在本教程中,您将涉及其中一个功能特性,即内置函数map()。您还将学习如何使用列表理解和生成器表达式以 Pythonic 化和可读的方式获得与map()相同的功能。
Python 的map() 入门
有时,您可能会遇到这样的情况,您需要对输入 iterable 的所有项执行相同的操作来构建新的 iterable。解决这个问题的最快和最常见的方法是使用一个 Python for循环。然而,您也可以通过使用map()在没有显式循环的情况下解决这个问题。
在接下来的三个部分中,您将了解到map()是如何工作的,以及如何使用它来处理和转换可重复数据而不产生循环。
理解map()
map()循环遍历一个输入 iterable(或多个 iterables)的项,并返回一个迭代器,该迭代器是通过对原始输入 iterable 中的每一项应用转换函数而得到的。
根据文档 , map()将一个函数对象和一个可迭代对象(或多个可迭代对象)作为参数,并返回一个迭代器,该迭代器根据需要生成转换后的项。该函数的签名定义如下:
map(function, iterable[, iterable1, iterable2,..., iterableN])
map()将function应用于循环中iterable中的每一项,并返回一个新的迭代器,该迭代器根据需要生成转换后的项。function可以是任何一个 Python 函数,它接受的参数数量等于传递给map()的可迭代次数。
注意:map()的第一个参数是一个函数对象,这意味着你需要传递一个函数而不需要调用它。也就是说,不使用一对括号。
map()的第一个参数是一个转换函数。换句话说,就是这个函数将每个原始项转换成一个新的(转换后的)项。即使 Python 文档调用这个参数function,它也可以是任何 Python 可调用的。这包括内置函数,类,方法, lambda函数,以及用户自定义函数。
map()执行的操作通常被称为映射,因为它将输入 iterable 中的每个项目映射到结果 iterable 中的一个新项目。为此,map()对输入 iterable 中的所有项应用一个转换函数。
为了更好地理解map(),假设您需要获取一个数值列表,并将其转换为包含原始列表中每个数字的平方值的列表。在这种情况下,您可以使用一个for循环并编写如下代码:
>>> numbers = [1, 2, 3, 4, 5]
>>> squared = []
>>> for num in numbers:
... squared.append(num ** 2)
...
>>> squared
[1, 4, 9, 16, 25]
当您在numbers上运行这个循环时,您会得到一个平方值列表。for循环对numbers进行迭代,并对每个值进行幂运算。最后,它将结果值存储在squared中。
通过使用map(),您可以在不使用显式循环的情况下获得相同的结果。看一下上面例子的重新实现:
>>> def square(number):
... return number ** 2
...
>>> numbers = [1, 2, 3, 4, 5]
>>> squared = map(square, numbers)
>>> list(squared)
[1, 4, 9, 16, 25]
square()是将数字映射到其平方值的变换函数。对map()的调用将square()应用于numbers中的所有值,并返回一个产生平方值的迭代器。然后在map()上调用list()来创建一个包含平方值的列表对象。
由于map()是用 C 编写的,并且经过了高度优化,其内部隐含循环可以比常规 Python for循环更高效。这是使用map()的一个优势。
使用map()的第二个优势与内存消耗有关。使用一个for循环,您需要将整个列表存储在系统内存中。使用map(),你可以按需获得物品,并且在给定的时间内,只有一个物品在你的系统内存中。
**注意:**在 Python 2.x 中, map() 返回一个列表。这种行为在 Python 3.x 中有所改变。现在,map()返回一个 map 对象,这是一个迭代器,可以按需生成条目。这就是为什么你需要调用list()来创建想要的列表对象。
再举一个例子,假设您需要将列表中的所有条目从一个字符串转换成一个整数。为此,您可以将map()与int()一起使用,如下所示:
>>> str_nums = ["4", "8", "6", "5", "3", "2", "8", "9", "2", "5"]
>>> int_nums = map(int, str_nums)
>>> int_nums
<map object at 0x7fb2c7e34c70>
>>> list(int_nums)
[4, 8, 6, 5, 3, 2, 8, 9, 2, 5]
>>> str_nums
["4", "8", "6", "5", "3", "2", "8", "9", "2", "5"]
map()将 int() 应用于str_nums中的每一个值。因为map()返回一个迭代器(一个 map 对象),所以你需要调用list(),这样你就可以用尽迭代器并把它变成一个 list 对象。请注意,原始序列在此过程中不会被修改。
将map()与不同种类的功能一起使用
您可以使用任何类型的可通过map()调用的 Python。唯一的条件是 callable 接受一个参数并返回一个具体的有用的值。例如,您可以使用类、实现名为 __call__() 的特殊方法的实例、实例方法、类方法、静态方法和函数。
有一些内置函数可以和map()一起使用。考虑下面的例子:
>>> numbers = [-2, -1, 0, 1, 2]
>>> abs_values = list(map(abs, numbers))
>>> abs_values
[2, 1, 0, 1, 2]
>>> list(map(float, numbers))
[-2.0, -1.0, 0.0, 1.0, 2.0]
>>> words = ["Welcome", "to", "Real", "Python"]
>>> list(map(len, words))
[7, 2, 4, 6]
您可以使用任何带有map()的内置函数,只要该函数接受一个参数并返回值。
使用map()的一个常见模式是使用lambda函数作为第一个参数。当您需要将基于表达式的函数传递给map()时,lambda函数非常方便。例如,您可以使用lambda函数重新实现平方值的示例,如下所示:
>>> numbers = [1, 2, 3, 4, 5]
>>> squared = map(lambda num: num ** 2, numbers)
>>> list(squared)
[1, 4, 9, 16, 25]
使用map()时,函数非常有用。他们可以起到第一个论证map()的作用。你可以使用lambda函数和map()来快速处理和转换你的可重复项。
用map() 处理多个输入项
如果您向map()提供多个 iterables,那么转换函数必须接受与您传入的 iterables 一样多的参数。map()的每次迭代都会将每个 iterable 中的一个值作为参数传递给function。迭代在最短的迭代结束时停止。
考虑下面这个使用 pow() 的例子:
>>> first_it = [1, 2, 3]
>>> second_it = [4, 5, 6, 7]
>>> list(map(pow, first_it, second_it))
[1, 32, 729]
pow()接受两个参数x和y,并将x返回给y的幂。第一次迭代,x会是1,y会是4,结果是1。在第二次迭代中,x将是2,y将是5,结果将是32,以此类推。最终的可迭代式只有最短的可迭代式那么长,在本例中是first_it。
这种技术允许您使用不同种类的数学运算来合并两个或多个数值的可迭代项。下面是一些使用lambda函数对几个输入变量执行不同数学运算的例子:
>>> list(map(lambda x, y: x - y, [2, 4, 6], [1, 3, 5]))
[1, 1, 1]
>>> list(map(lambda x, y, z: x + y + z, [2, 4], [1, 3], [7, 8]))
[10, 15]
在第一个示例中,您使用减法运算来合并两个各包含三项的 iterables。在第二个示例中,您将三个 iterables 的值相加。
用 Python 的map() 转换字符串的可重复项
当您处理 string 对象的 iterables 时,您可能会对使用某种转换函数转换所有对象感兴趣。在这些情况下,Python 的map()可以成为你的盟友。接下来的部分将带您浏览一些如何使用map()来转换 string 对象的 iterables 的例子。
使用str的方法
一种很常见的字符串操作方法是使用类str 的一些方法将一个给定的字符串转换成一个新的字符串。如果您正在处理字符串的可重复项,并且需要对每个字符串应用相同的转换,那么您可以使用map()和各种字符串方法:
>>> string_it = ["processing", "strings", "with", "map"]
>>> list(map(str.capitalize, string_it))
['Processing', 'Strings', 'With', 'Map']
>>> list(map(str.upper, string_it))
['PROCESSING', 'STRINGS', 'WITH', 'MAP']
>>> list(map(str.lower, string_it))
['processing', 'strings', 'with', 'map']
您可以使用map()和 string 方法对string_it中的每一项执行一些转换。大多数时候,你会使用不带附加参数的方法,比如 str.capitalize() 、 str.lower() 、 str.swapcase() 、 str.title() 和 str.upper() 。
您还可以使用一些方法,这些方法采用带有默认值的附加参数,例如 str.strip() ,它采用一个名为char的可选参数,默认情况下移除空白:
>>> with_spaces = ["processing ", " strings", "with ", " map "]
>>> list(map(str.strip, with_spaces))
['processing', 'strings', 'with', 'map']
当你像这样使用str.strip()时,你依赖于char的默认值。在这种情况下,使用map()删除with_spaces条目中的所有空格。
**注意:**如果你需要提供参数而不是依赖默认值,那么你可以使用lambda函数。
下面是一个使用str.strip()来删除点而不是默认空白的例子:
>>> with_dots = ["processing..", "...strings", "with....", "..map.."]
>>> list(map(lambda s: s.strip("."), with_dots))
['processing', 'strings', 'with', 'map']
lambda函数调用字符串对象s上的.strip()并删除所有的前导和尾随点。
例如,当您处理文本文件时,其中的行可能有尾随空格(或其他字符)并且您需要删除它们,这种技术会很方便。如果是这种情况,那么您需要考虑在没有自定义char的情况下使用str.strip()也会删除换行符。
删除标点符号
在处理文本时,有时需要删除将文本拆分成单词后留下的标点符号。为了解决这个问题,您可以创建一个自定义函数,使用一个匹配最常见标点符号的正则表达式来删除单个单词的标点符号。
下面是使用 sub() 实现该函数的一种可能,T3 是一个正则表达式函数,位于 Python 标准库中的 re 模块中:
>>> import re
>>> def remove_punctuation(word):
... return re.sub(r'[!?.:;,"()-]', "", word)
>>> remove_punctuation("...Python!")
'Python'
在remove_punctuation()中,您使用了一个正则表达式模式,该模式匹配任何英文文本中最常见的标点符号。对re.sub()的调用使用空字符串("")替换匹配的标点符号,并返回一个干净的word。
有了转换函数,您可以使用map()对文本中的每个单词进行转换。它是这样工作的:
>>> text = """Some people, when confronted with a problem, think
... "I know, I'll use regular expressions."
... Now they have two problems. Jamie Zawinski"""
>>> words = text.split()
>>> words
['Some', 'people,', 'when', 'confronted', 'with', 'a', 'problem,', 'think'
, '"I', 'know,', "I'll", 'use', 'regular', 'expressions."', 'Now', 'they',
'have', 'two', 'problems.', 'Jamie', 'Zawinski']
>>> list(map(remove_punctuation, words))
['Some', 'people', 'when', 'confronted', 'with', 'a', 'problem', 'think',
'I', 'know', "I'll", 'use', 'regular', 'expressions', 'Now', 'they', 'have
', 'two', 'problems', 'Jamie', 'Zawinski']
在这段文字中,有些单词包含标点符号。例如,你用'people,'代替'people',用'problem,'代替'problem',等等。对map()的调用将remove_punctuation()应用于每个单词,并删除任何标点符号。所以,在第二个list中,你已经清理了单词。
请注意,撇号(')不在您的正则表达式中,因为您希望像I'll这样的缩写保持原样。
实现凯撒密码算法
罗马政治家朱利叶斯·凯撒,曾用一种密码对他发送给他的将军们的信息进行加密保护。一个凯撒密码将每个字母移动若干个字母。例如,如果将字母a移动三位,则得到字母d,依此类推。
如果移位超出了字母表的末尾,那么你只需要旋转回到字母表的开头。在旋转三次的情况下,x将变成a。这是旋转后字母表的样子:
- 原字母表:
abcdefghijklmnopqrstuvwxyz - 字母旋转三次:
defghijklmnopqrstuvwxyzabc
下面的代码实现了rotate_chr(),这个函数获取一个字符并将其旋转三圈。rotate_chr()将返回旋转后的字符。代码如下:
1def rotate_chr(c):
2 rot_by = 3
3 c = c.lower()
4 alphabet = "abcdefghijklmnopqrstuvwxyz"
5 # Keep punctuation and whitespace 6 if c not in alphabet:
7 return c
8 rotated_pos = ord(c) + rot_by
9 # If the rotation is inside the alphabet 10 if rotated_pos <= ord(alphabet[-1]):
11 return chr(rotated_pos)
12 # If the rotation goes beyond the alphabet 13 return chr(rotated_pos - len(alphabet))
在rotate_chr()中,你首先检查这个字符是否在字母表中。如果不是,那么你返回相同的字符。这样做的目的是保留标点符号和其他不常用的字符。在第 8 行,您计算字符在字母表中新的旋转位置。为此,您使用内置函数 ord() 。
ord()接受一个 Unicode 字符并返回一个表示输入字符的 Unicode 码位的整数。比如ord("a")返回97,ord("b")返回98:
>>> ord("a")
97
>>> ord("b")
98
ord()以字符为参数,返回输入字符的 Unicode 码位。
如果你把这个整数加到目标数字rot_by上,那么你将得到新字母在字母表中的旋转位置。在这个例子中,rot_by就是3。所以,字母"a"旋转三圈后将成为位置100的字母,也就是字母"d"。字母"b"旋转三个会变成位置101的字母,也就是字母"e",以此类推。
如果字母的新位置没有超出最后一个字母(alphabet[-1])的位置,那么就在这个新位置返回字母。为此,您使用内置函数 chr() 。
chr()是ord()的逆。它接受一个表示 Unicode 字符的 Unicode 码位的整数,并返回该位置的字符。比如chr(97)会返回'a',chr(98)会返回'b':
>>> chr(97)
'a'
>>> chr(98)
'b'
chr()取一个表示字符的 Unicode 码位的整数,并返回相应的字符。
最后,如果新的旋转位置超出了最后一个字母(alphabet[-1])的位置,那么就需要旋转回到字母表的开头。为此,您需要从旋转后的位置(rotated_pos - len(alphabet))减去字母表的长度,然后使用chr()将字母返回到新的位置。
用rotate_chr()作为你的变换函数,你可以用map()用凯撒密码算法加密任何文本。下面是一个使用 str.join() 连接字符串的例子:
>>> "".join(map(rotate_chr, "My secret message goes here."))
'pb vhfuhw phvvdjh jrhv khuh.'
字符串在 Python 中也是可迭代的。因此,对map()的调用将rotate_chr()应用于原始输入字符串中的每个字符。在这种情况下,"M"变成了"p","y"变成了"b",以此类推。最后,对str.join()的调用将最终加密消息中的每个旋转字符连接起来。
用 Python 的map() 转换数字的迭代式
map()在处理和转换数值的迭代时也有很大的潜力。您可以执行各种数学和算术运算,将字符串值转换为浮点数或整数,等等。
在接下来的几节中,您将会看到一些如何使用map()来处理和转换数字的迭代的例子。
使用数学运算
使用数学运算转换可迭代数值的一个常见例子是使用幂运算符(** ) 。在下面的示例中,您编写了一个转换函数,该函数接受一个数字并返回该数字的平方和立方:
>>> def powers(x):
... return x ** 2, x ** 3
...
>>> numbers = [1, 2, 3, 4]
>>> list(map(powers, numbers))
[(1, 1), (4, 8), (9, 27), (16, 64)]
powers()取一个数x并返回它的平方和立方。由于 Python 将多个返回值作为元组来处理,所以每次调用powers()都会返回一个包含两个值的元组。当您使用powers()作为参数调用map()时,您会得到一个元组列表,其中包含输入 iterable 中每个数字的平方和立方。
使用map()可以执行许多与数学相关的转换。您可以向每个值中添加常数,也可以从每个值中减去常数。您还可以使用 math模块中的一些功能,如 sqrt() 、 factorial() 、 sin() 、 cos() 等等。这里有一个使用factorial()的例子:
>>> import math
>>> numbers = [1, 2, 3, 4, 5, 6, 7]
>>> list(map(math.factorial, numbers))
[1, 2, 6, 24, 120, 720, 5040]
在这种情况下,您将numbers转换成一个新列表,其中包含原始列表中每个数字的阶乘。
您可以使用map()对可迭代的数字执行各种数学转换。你能在这个话题上走多远将取决于你的需求和你的想象力。考虑一下,编写你自己的例子!
转换温度
map()的另一个用例是在测量单位之间进行转换。假设您有一个以摄氏度或华氏度测量的温度列表,您需要将它们转换为以华氏度或摄氏度为单位的相应温度。
您可以编写两个转换函数来完成这项任务:
def to_fahrenheit(c):
return 9 / 5 * c + 32
def to_celsius(f):
return (f - 32) * 5 / 9
to_fahrenheit()以摄氏度为单位进行温度测量,并将其转换为华氏度。类似地,to_celsius()采用华氏温度并将其转换为摄氏温度。
这些函数就是你的转换函数。您可以将它们与map()一起使用,分别将温度测量值转换为华氏温度和摄氏温度:
>>> celsius_temps = [100, 40, 80]
>>> # Convert to Fahrenheit
>>> list(map(to_fahrenheit, celsius_temps))
[212.0, 104.0, 176.0]
>>> fahr_temps = [212, 104, 176]
>>> # Convert to Celsius
>>> list(map(to_celsius, fahr_temps))
[100.0, 40.0, 80.0]
如果你用to_fahrenheit()和celsius_temps调用map(),那么你会得到一个华氏温度的度量列表。如果您用to_celsius()和fahr_temps调用map(),那么您会得到一个以摄氏度为单位的温度测量列表。
为了扩展这个例子并涵盖任何其他类型的单位转换,您只需要编写一个适当的转换函数。
将字符串转换为数字
当处理数字数据时,您可能会遇到所有数据都是字符串值的情况。要做进一步的计算,您需要将字符串值转换成数值。对这些情况也有帮助。
如果你确定你的数据是干净的,没有包含错误的值,那么你可以根据你的需要直接使用 float() 或者int()。以下是一些例子:
>>> # Convert to floating-point
>>> list(map(float, ["12.3", "3.3", "-15.2"]))
[12.3, 3.3, -15.2]
>>> # Convert to integer
>>> list(map(int, ["12", "3", "-15"]))
[12, 3, -15]
在第一个例子中,您使用float()和map()将所有值从字符串值转换为浮点值。在第二种情况下,使用int()将字符串转换为整数。注意,如果其中一个值不是有效的数字,那么您将得到一个 ValueError 。
如果您不确定您的数据是否干净,那么您可以使用一个更复杂的转换函数,如下所示:
>>> def to_float(number):
... try:
... return float(number.replace(",", "."))
... except ValueError:
... return float("nan")
...
>>> list(map(to_float, ["12.3", "3,3", "-15.2", "One"]))
[12.3, 3.3, -15.2, nan]
在to_float()中,您使用了一个 try语句,如果在转换number时float()失败,该语句将捕获一个ValueError。如果没有错误发生,那么您的函数返回转换成有效浮点数的number。否则,您会得到一个 nan(不是数字)值,这是一个特殊的float值,您可以用它来表示不是有效数字的值,就像上面例子中的"One"一样。
可以根据需要定制to_float()。例如,您可以用语句return 0.0替换语句return float("nan"),等等。
将map()与其他功能工具结合
到目前为止,您已经讲述了如何使用map()来完成不同的涉及 iterables 的任务。但是,如果您使用map()和其他功能工具,如 filter() 和 reduce() ,那么您可以对您的可迭代对象执行更复杂的转换。这就是您将在接下来的两个部分中涉及的内容。
map()和filter()
有时,您需要处理一个输入可迭代对象,并返回另一个可迭代对象,该对象是通过过滤掉输入可迭代对象中不需要的值而得到的。那样的话,Python 的 filter() 可以是你不错的选择。filter()是一个内置函数,接受两个位置参数:
function将是一个谓词或布尔值函数,一个根据输入数据返回True或False的函数。iterable将是任何 Python 可迭代的。
filter()产生function返回True的输入iterable的项目。如果您将None传递给function,那么filter()将使用 identity 函数。这意味着filter()将检查iterable中每个项目的真值,并过滤掉所有为假值的项目。
为了说明如何使用map()和filter(),假设您需要计算列表中所有值的平方根。因为您的列表可能包含负值,所以您会得到一个错误,因为平方根不是为负数定义的:
>>> import math
>>> math.sqrt(-16)
Traceback (most recent call last):
File "<input>", line 1, in <module>
math.sqrt(-16)
ValueError: math domain error
以负数为自变量,math.sqrt()引出一个ValueError。为了避免这个问题,你可以使用filter()来过滤掉所有的负值,然后找到剩余正值的平方根。看看下面的例子:
>>> import math
>>> def is_positive(num):
... return num >= 0
...
>>> def sanitized_sqrt(numbers):
... cleaned_iter = map(math.sqrt, filter(is_positive, numbers))
... return list(cleaned_iter)
...
>>> sanitized_sqrt([25, 9, 81, -16, 0])
[5.0, 3.0, 9.0, 0.0]
is_positive()是一个谓词函数,它将一个数字作为参数,如果该数字大于或等于零,则返回True。你可以通过is_positive()到filter()来清除numbers的所有负数。因此,对map()的调用将只处理正数,而math.sqrt()不会给你一个ValueError。
map()和reduce()
Python 的 reduce() 是一个函数,驻留在 Python 标准库中一个名为 functools 的模块中。reduce()是 Python 中的另一个核心函数工具,当你需要将一个函数应用于一个可迭代对象并将其简化为一个累积值时,它非常有用。这种操作俗称 缩小或 。reduce()需要两个参数:
function可以是任何接受两个参数并返回值的 Python 可调用函数。iterable可以是任何 Python 可迭代的。
reduce()将function应用于iterable中的所有项目,并累计计算出最终值。
下面这个例子结合了map()和reduce()来计算您的主目录中所有文件的累积总大小:
>>> import functools
>>> import operator
>>> import os
>>> import os.path
>>> files = os.listdir(os.path.expanduser("~"))
>>> functools.reduce(operator.add, map(os.path.getsize, files))
4377381
在这个例子中,您调用 os.path.expanduser("~") 来获得您的主目录的路径。然后你调用这个路径上的 os.listdir() 来获得一个包含所有文件路径的列表。
对map()的调用使用 os.path.getsize() 来获取每个文件的大小。最后,您使用reduce()和 operator.add() 来获得每个文件大小的累积和。最终结果是您的主目录中所有文件的总大小,以字节为单位。
**注:**几年前,谷歌开发并开始使用一种编程模型,他们称之为 MapReduce 。这是一种新的数据处理方式,旨在使用集群上的并行和分布式计算来管理大数据。
这个模型的灵感来自于函数式编程中常用的映射和归约操作的组合。
MapReduce 模型对谷歌在合理的时间内处理大量数据的能力产生了巨大的影响。然而,到 2014 年,谷歌不再使用 MapReduce 作为他们的主要处理模型。
如今,你可以找到一些 MapReduce 的替代实现,比如 Apache Hadoop T1,这是一个使用 MapReduce 模型的开源软件工具的集合。
尽管您可以使用reduce()来解决本节中涉及的问题,但是 Python 提供了其他工具来实现更 Python 化和更高效的解决方案。例如,您可以使用内置函数sum()来计算您的主目录中文件的总大小:
>>> import os
>>> import os.path
>>> files = os.listdir(os.path.expanduser("~"))
>>> sum(map(os.path.getsize, files))
4377381
这个例子比你之前看到的例子可读性更强,效率更高。如果您想更深入地了解如何使用reduce()以及可以使用哪些替代工具以 Python 的方式取代reduce(),那么请查看 Python 的 reduce():从函数式到 Python 式。
用starmap() 处理基于元组的可重复项
Python 的 itertools.starmap() 构造了一个迭代器,它将函数应用于从元组的可迭代对象中获得的参数,并产生结果。当您处理已经分组为元组的可重复项时,这很有用。
map()和starmap()的主要区别在于,后者使用解包操作符(* ) 调用其转换函数,将每个参数元组解包成几个位置参数。因此,转换函数被称为function(*args),而不是function(arg1, arg2,... argN)。
starmap() 的官方文档称该函数大致相当于以下 Python 函数:
def starmap(function, iterable):
for args in iterable:
yield function(*args)
该函数中的for循环迭代iterable中的项目,并产生转换后的项目。对function(*args)的调用使用解包操作符将元组解包成几个位置参数。下面是一些starmap()如何工作的例子:
>>> from itertools import starmap
>>> list(starmap(pow, [(2, 7), (4, 3)]))
[128, 64]
>>> list(starmap(ord, [(2, 7), (4, 3)]))
Traceback (most recent call last):
File "<input>", line 1, in <module>
list(starmap(ord, [(2, 7), (4, 3)]))
TypeError: ord() takes exactly one argument (2 given)
在第一个示例中,您使用 pow() 来计算每个元组中第一个值的第二次幂。元组将采用(base, exponent)的形式。
如果 iterable 中的每个元组都有两个条目,那么function也必须有两个参数。如果元组有三项,那么function必须有三个参数,依此类推。否则,你会得到一个 TypeError 。
如果您使用map()而不是starmap(),那么您将得到不同的结果,因为map()从每个元组中取出一个项目:
>>> list(map(pow, (2, 7), (4, 3)))
[16, 343]
注意map()采用两个元组,而不是一个元组列表。map()在每次迭代中也从每个元组中取一个值。为了让map()返回与starmap()相同的结果,您需要交换值:
>>> list(map(pow, (2, 4), (7, 3)))
[128, 64]
在这种情况下,您有两个元组,而不是一个元组列表。你还交换了7和4。现在,第一个元组提供了基数,第二个元组提供了指数。
Pythonic 风格编码:替换map()
像map()、filter()和reduce()这样的函数式编程工具已经存在很久了。然而,列表理解和生成器表达式几乎在每个用例中都成为它们的自然替代品。
例如,map()提供的功能几乎总是用列表理解或生成器表达式来更好地表达。在接下来的两节中,您将学习如何用列表理解或生成器表达式替换对map()的调用,以使您的代码更具可读性和 Pythonic 性。
使用列表理解
有一个通用的模式,你可以用列表理解来代替对map()的调用。方法如下:
# Generating a list with map
list(map(function, iterable))
# Generating a list with a list comprehension
[function(x) for x in iterable]
注意,列表理解几乎总是比调用map()读起来更清楚。因为列表理解在 Python 开发人员中非常流行,所以到处都能找到它们。因此,用列表理解替换对map()的调用将使您的代码对其他 Python 开发人员来说更熟悉。
Here’s an example of how to replace map() with a list comprehension to build a list of square numbers:
>>> # Transformation function
>>> def square(number):
... return number ** 2
>>> numbers = [1, 2, 3, 4, 5, 6]
>>> # Using map()
>>> list(map(square, numbers))
[1, 4, 9, 16, 25, 36]
>>> # Using a list comprehension
>>> [square(x) for x in numbers]
[1, 4, 9, 16, 25, 36]
如果你比较两种解决方案,那么你可能会说,使用列表理解的解决方案更具可读性,因为它读起来几乎像普通英语。此外,列表理解避免了显式调用map()上的list()来构建最终列表的需要。
使用生成器表达式
map()返回一个地图对象,它是一个迭代器,根据需要生成项目。因此,map()的自然替代物是生成器表达式,因为生成器表达式返回生成器对象,这些对象也是按需生成项目的迭代器。
众所周知,Python 迭代器在内存消耗方面非常高效。这就是为什么map()现在返回迭代器而不是list的原因。
列表理解和生成器表达式之间有微小的语法差异。第一个使用一对方括号([])来分隔表达式。第二种使用一对括号(())。因此,要将列表理解转化为生成器表达式,只需用圆括号替换方括号。
您可以使用生成器表达式来编写比使用map()的代码更清晰的代码。看看下面的例子:
>>> # Transformation function
>>> def square(number):
... return number ** 2
>>> numbers = [1, 2, 3, 4, 5, 6]
>>> # Using map()
>>> map_obj = map(square, numbers)
>>> map_obj
<map object at 0x7f254d180a60>
>>> list(map_obj)
[1, 4, 9, 16, 25, 36]
>>> # Using a generator expression
>>> gen_exp = (square(x) for x in numbers)
>>> gen_exp
<generator object <genexpr> at 0x7f254e056890>
>>> list(gen_exp)
[1, 4, 9, 16, 25, 36]
这段代码与上一节中的代码有一个主要区别:您将方括号改为一对圆括号,将列表理解转换为生成器表达式。
生成器表达式通常用作函数调用中的参数。在这种情况下,您不需要使用括号来创建生成器表达式,因为用于调用函数的括号也提供了构建生成器的语法。有了这个想法,你可以像这样调用list()得到和上面例子一样的结果:
>>> list(square(x) for x in numbers)
[1, 4, 9, 16, 25, 36]
如果在函数调用中使用生成器表达式作为参数,那么就不需要额外的一对括号。用于调用函数的括号提供了构建生成器的语法。
生成器表达式在内存消耗方面和map()一样高效,因为它们都返回按需生成条目的迭代器。然而,生成器表达式几乎总能提高代码的可读性。在其他 Python 开发人员看来,它们也使您的代码更加 Python 化。
结论
Python 的 map() 可以让你对 iterables 进行 映射 操作。映射操作包括将转换函数应用于 iterable 中的项目,以生成转换后的 iterable。一般来说,map()将允许您在不使用显式循环的情况下处理和转换可迭代对象。
在本教程中,您已经学习了map()是如何工作的,以及如何使用它来处理 iterables。您还了解了一些可以用来替换代码中的map()的python 化的工具。
你现在知道如何:
- 用 Python 创作的
map() - 使用
map()到处理和转换迭代,而不使用显式循环 - 将
map()与filter()和reduce()等函数结合起来执行复杂的变换 - 用类似于列表理解和生成器表达式的工具替换
map()
有了这些新知识,你将能够在你的代码中使用map(),并且用函数式编程风格来处理你的代码。您还可以通过用一个列表理解或一个生成器表达式替换map()来切换到一个更 Pythonic 和现代的风格。
立即观看本教程有真实 Python 团队创建的相关视频课程。和文字教程一起看,加深理解: Python 的 map()函数:变换 Iterables*********