转自公众号数据STUDIO
Python 中的 map() 是一个内置函数,可以在不使用显式 for 循环的情况下处理和转换一个迭代器中的所有项目,这种技术通常被称为映射。当需要对一个迭代器中的每个项目应用一个转换函数并将它们转换成一个新的迭代器时,map() 是非常有用的。map() 是支持 Python 中函数式编程风格的工具之一。
本文中,我们将一起学习
Python的 map() 如何工作;
如何使用 map() 转换不同类型的 Python 迭代变量。
有了这些知识,我们就能在程序中有效地使用map()
,或者使用列表推导式或生成器表达式来使代码更加Pythonic。
为了更好地理解map()
,需要一些前置知识,如了解如何使用迭代器、for循环、函数和lambda函数的一些知识。
可以查看我们日前详细介绍过的戳👉:Python 中的万能之王 Lambda 函数
Python中的函数式编程
在函数式编程中,计算是通过组合函数来完成的,这些函数接受参数并返回一个(或多个)具体数值作为结果。这些函数不修改其输入参数,也不改变程序的状态。它们只是提供一个特定的计算结果。这些类型的函数通常被称为纯函数。
从理论上讲,使用函数式编程风格构建的程序将更易运行。
开发,因为我们可以单独编码和使用每一个函数
调试和测试,因为我们可以测试和调试个别功能,而不看程序的其他部分。
理解,因为我们不需要处理整个程序的状态变化
函数式编程通常使用列表、数组和其他可迭代的数据类型,以及一组对数据进行操作和转换的函数来表示。当涉及到用函数式处理数据时,至少有三种常用的技术。
Mapping
包括对可迭代对象应用转换函数来生成新的可迭代对象。通过对原始可迭代对象中的每一项调用转换函数来生成新的可迭代对象中的项。Filtering
包括对可迭代对象应用谓词或布尔值函数来生成新的可迭代对象。新可迭代对象中的项是通过过滤掉原始可迭代对象中使谓词函数返回False的任何项来生成的。Reducing
括对可迭代对象应用reduce函数来产生单个累积值。
早在1993年,Python社区就要求有一些函数式编程的特性,他们要求的是
一个
Lambda
匿名函数一个
map()
函数一个
filter()
函数一个
reduce()
函数
由于一位社区成员的贡献,这些函数式特性被添加到语言中。现在,map()
、filter()
和reduce()
是Python中函数式编程风格的基本组成部分。
在本文中,我们将了解这些函数之一,即内置函数map()
。我们还将学习如何使用列表推导式和生成器表达式,以Pythonic和可读的方式获得map()
的相同功能。
开始使用 map() 函数
有时我们会面临这样的情况:需要对输入可迭代对象的所有项执行相同的操作来构建一个新的可迭代对象。解决这个问题的最快速、最常见的方法是使用一个 Python for 循环。然而也可以通过使用 map()
来解决这个问题,而不需要显式循环。
在下面,我们将一起学习 map()
的工作原理,以及如何使用它来处理和转换迭代变量而不需要循环。
入门 map()
map()
循环遍历输入可迭代对象(或可迭代对象)的项,并返回一个迭代器,该迭代器是对原始输入可迭代对象中的每个项应用转换函数得到的结果。
根据 文档[1],map()
接受一个函数对象和一个可迭代对象(或多个可迭代对象)作为参数,并返回一个迭代器,根据需要生成转换后的条目。函数签名定义如下:
map(function, iterable[, iterable1, iterable2,..., iterableN] )
map()
将函数应用于循环中可迭代对象中的每个元素,并返回一个新的迭代器,按需生成转换后的元素。函数可以是任何Python函数,其参数的数量等于传递给 map()
的可迭代对象的数量。
注意:
map()
的第一个参数是一个函数对象,需要传递一个函数而不调用它。即不需要使用一对圆括号。
map()
的这个第一个参数是一个转换函数。换句话说,它是将每个原始项转化为新的 (已转化) 项的函数。尽管 Python 文档中称这个参数为函数,但它可以是任何 Python 可调用的函数,这包括内置函数、类、方法、lambda 函数和用户定义的函数。
map()
执行的操作通常被称为mapping,因为它将输入可迭代对象中的每一项映射到结果可迭代对象中的新项。为此,map()
对输入的可迭代对象中的所有项应用一个转换函数。
为了更好地理解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可调用函数。唯一的条件是该可调用函数需要一个参数,并返回一个具体而有用的值。例如,我们可以使用类、实现了特殊方法 __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()
时,lambda函数是相当有用的。它们可以发挥 map()
的第一个参数的作用。我们可以在使用 map()
的同时使用lambda函数来快速处理和转换我们的迭代变量。
用map()处理多个输入可迭代对象
如果为map()
提供了多个迭代变量,那么转换函数必须接受与传递的迭代变量同样多的参数。map()
的每一次迭代将从每个迭代器中传递一个值作为参数给函数,迭代在最短的迭代器的末端停止。
以下使用 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]
在第一个示例中,使用减法操作合并两个可迭代对象,每个可迭代对象包含三个元素。在第二个示例中,将三个可迭代对象的值相加。
用map()转换字符串的可迭代对象
当处理字符串对象的可迭代对象时,我们可以借助Python的map()
,使用某种转换函数转换所有对象。下面我们通过一些示例,了解如何使用 map()
转换字符串对象的可迭代对象。
使用 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()
和str
方法,可以对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()
,删除所有前导和尾部的点。
例如,当处理文本文件时,其中的行可能有尾部的空格(或其他字符),而需要删除它们时,这种技术就会很方便。如果是这种情况,那么需要考虑使用str.strip()
而不使用自定义字符,也会删除换行字符。
删除标点符号
有时在处理文本时,需要删除将文本分割成单词后留下的标点符号。我们可以创建一个自定义函数,使用匹配最常见的标点符号的正则表达式来删除单个单词的标点符号。
下面是使用sub()[2]实现该函数的可能方法,它是一个存在于Python标准库中的re[3]模块中的正则表达式函数:
>>> import re
>>> def remove_punctuation(word):
... return re.sub(r'[!?.:;,"()-]', "", word)
>>> remove_punctuation("...Python!")
'Python'
在自定义函数remove_punctuation()
中,使用一个正则表达式模式来匹配在任何英文文本中发现的最常见的标点符号。对re.sub()
的调用使用一个空字符串(""
)替换了匹配的标点符号,并返回一个干净的单词。
有了转换函数,我们可以使用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()
应用于每个词,并删除任何标点符号。因此,第二个列表中存储的是处理后的干净的单词。
注意我们的正则表达式中没有撇号('
),因为我们想将像I'll
这样的缩略语保持原貌。
实现凯撒密码算法
罗马政治家Julius Caesar
曾经用密码来保护他发给他的将军们的信息,用密码来加密。Caesar cipher 凯撒密码[4]将每个字母移位若干个字母。例如,如果我们将字母a
移位三个,那么我们就会得到字母d
,以此类推。
如果移位超过了字母表的末端,那么我们只需要旋转回字母表的开头。在旋转3的情况下,x
会变成a
。以下是旋转后字母表的样子。
原文字母: abcdefghijklmnopqrstuvwxyz
字母表旋转3: defghijklmnopqrstuvwxyzabc
下面的代码实现了rotate_chr()
,这个函数接收一个字符并将其旋转3圈。rotate_chr()
将返回旋转后的字符。下面是代码。
def rotate_chr(c):
rot_by = 3
c = c.lower()
alphabet = "abcdefghijklmnopqrstuvwxyz"
# 保留标点符号和空白处
if c not in alphabet:
return c
rotated_pos = ord(c) + rot_by
# 如果旋转的位置在字母表内
if rotated_pos <= ord(alphabet[-1]):
return chr(rotated_pos)
# 如果旋转超出了字母表的范围
return chr(rotated_pos - len(alphabet))
在rotate_chr()
中,首先检查该字符是否在字母表中。如果不是,那么就返回相同的字符。这样做的目的是为了保留标点符号和其他不寻常的字符。在第8行,计算该字符在字母表中的新的旋转位置,这里要使用内置函数ord()[5]。
ord()
接收一个Unicode字符,并返回一个整数,代表输入字符的 Unicode码位。例如,ord("a")
返回97
,而ord("b")
返回98
。
>>> ord("a")
97
>>> ord("b")
98
ord()
接收一个字符作为参数,并返回输入字符的Unicode码位。
如果把这个整数加到rot_by
的目标数上,那么将得到新字母在字母表中的旋转位置。在这个例子中,rot_by
是3。因此,字母 "a"
旋转3下将成为位置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()
的调用将每个旋转的字符连接到一个最终的加密信息中。
用map()转换数字的可迭代对象
map()
在处理和转换数字值的迭代表方面也有很大潜力。可以进行各种各样的数学和算术运算,将字符串值转换成浮点数或整数,等等。
在下面的内容中,我们将一起学习一些如何使用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']
powers()
接受一个数字'x'
并返回它的平方和立方。由于Python将多个返回值作为元组处理,所以每次调用powers()
都会返回一个有两个值的元组。当你用 powers()
作为参数调用 map()
时,你会得到一个包含输入可迭代对象中每个数字的平方和立方的元组列表。
使用 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]
在这种情况下,我们将数字转化为一个新的列表,包含原列表中每个数字的阶乘。
我们可以使用'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] 。
>>> # 转换为华氏温度
>>> list(map(to_fahrenheit, celsius_temps))
[212.0, 104.0, 176.0]
>>> fahr_temps = [212, 104, 176] 。
>>> # 转换为摄氏度
>>> list(map(to_celsius, fahr_temps))
[100.0, 40.0, 80.0]
如果用to_fahrenheit()
和celsius_temps
调用map()
,那么会得到一个华氏温度测量值的列表。如果用to_celsius()
和fahr_temps
来调用map()
,那么会得到一个以摄氏度为单位的温度测量列表。
要扩展这个例子并涵盖任何其他种类的单位转换,只需要编码一个适当的转换函数。
将字符串转换为数字
在处理数字数据时,我们可能会遇到所有数据都是字符串值的情况。要做任何进一步的计算,我们需要将字符串值转换成数字值。map()
也可以帮助处理这些情况。
如果我们确定我们的数据是干净的,不包含错误的值,那么我们可以根据我们的需要直接使用float()
或int()
。下面是一些例子。
>>> # 转换为浮点
>>> list(map(float, ["12.3", "3.3", "-15.2"])
[12.3, 3.3, -15.2]
>>> # 转换为整数
>>> 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() 与其他函数工具 结合起来,并且为了使代码更加Pythonic,使用列表推导式和生成器表达式来 替代 map() ,尽情期待~记得点赞和在看哦~
参考资料
[1]map()文档: https://docs.python.org/3/library/functions.html#map
[2]sub(): https://docs.python.org/3/library/re.html#re.sub
[3]re: https://docs.python.org/3/library/re.html#module-re
[4]Caesar cipher 凯撒密码: https://en.wikipedia.org/wiki/Caesar_cipher
[5]ord(): https://docs.python.org/3/library/functions.html#ord