新聞中心
本文將學(xué)習(xí)如何將 map() 與其他函數(shù)工具結(jié)合起來,并進(jìn)行更復(fù)雜的轉(zhuǎn)換;并學(xué)習(xí)可以用什么工具來替代map(),使代碼更加Pythonic。

map() 與其他函數(shù)結(jié)合使用
現(xiàn)在我們已經(jīng)介紹了如何使用 map() 來完成涉及迭代表的不同任務(wù)。然而,如果將map()?與其他函數(shù)式工具(如filter()? 和 reduce())一起使用,那么我們可以對(duì)迭代變量進(jìn)行更復(fù)雜的轉(zhuǎn)換。這就是在下面要介紹的內(nèi)容。
map() 和 filter()
有時(shí)需要處理一個(gè)輸入迭代器,并返回另一個(gè)迭代器,這個(gè)迭代器是過濾掉輸入迭代器中不需要的值而得到的。在這種情況下,Python 的 filter()? 可以是一個(gè)很好的選擇。filter() 是一個(gè)內(nèi)置的函數(shù),需要兩個(gè)位置參數(shù)。
- function 函數(shù)將是一個(gè)謂詞或布爾值函數(shù),一個(gè)根據(jù)輸入數(shù)據(jù)返回真或假的函數(shù)。
- iterable 是任何Python可迭代對(duì)象。
filter()? 產(chǎn)生的是函數(shù)返回 True 的輸入迭代器的項(xiàng)目。如果把 None傳給函數(shù),那么 filter()? 使用身份函數(shù)。這意味著 filter() 將檢查可迭代對(duì)象中每個(gè)項(xiàng)目的真值,并過濾掉所有虛假的項(xiàng)目。
為了說明如何使用 map()? 和 filter(),假設(shè)我們需要計(jì)算一個(gè)列表中所有數(shù)值的平方根,如果列表中包含負(fù)值時(shí),會(huì)得到一個(gè)錯(cuò)誤。
>>> import math
>>> math.sqrt(-16)
Traceback (most recent call last):
File "", line 1, in
math.sqrt(-16)
ValueError: math domain error
如果有一個(gè)負(fù)數(shù)作為參數(shù),math.sqrt()?會(huì)引發(fā)一個(gè)ValueError。為了避免這個(gè)問題,可以使用filter()來過濾掉所有的負(fù)值,然后找到剩余正值的平方根??聪旅娴睦?。
>>> 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()? 是一個(gè)謂詞函數(shù),它接受一個(gè)數(shù)字作為參數(shù),如果該數(shù)字大于或等于0,則返回True??梢詫?nbsp;is_positive()? 傳遞給 filter()?,以去除數(shù)字中的所有負(fù)數(shù)。因此對(duì) map()? 的調(diào)用將只處理正數(shù),從而確保 math.sqrt() 不會(huì)拋出一個(gè)ValueError。
map() 和 reduce()
reduce()? 是一個(gè)函數(shù),它存在于 Python 標(biāo)準(zhǔn)庫(kù)中一個(gè)叫做 functools? 的模塊中。reduce() ?是 Python 的另一個(gè)核心函數(shù)工具,當(dāng)我們需要將一個(gè)函數(shù)應(yīng)用于一個(gè)迭代器并將其減少到一個(gè)單一的累積值時(shí),它非常有用,這種操作通常被稱為減少或折疊。reduce() 需要兩個(gè)必要的參數(shù)。
- function 函數(shù)可以是任何接受兩個(gè)參數(shù)并返回一個(gè)值的 Python 可調(diào)用函數(shù)。
- iterable 可以是任何 Python 的可迭代對(duì)象。
reduce() 將對(duì)可迭代對(duì)象 iterable 中的所有項(xiàng)目應(yīng)用函數(shù),并累積計(jì)算出一個(gè)最終值。
下面的例子結(jié)合了 map()? 和 reduce() 來計(jì)算主目錄中所有文件的總大小:
>>> 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
在這個(gè)例子中,我們調(diào)用os.path.expanduser("~")?來獲得主目錄的路徑。然后在該路徑上調(diào)用 os.listdir() ,得到一個(gè)包含所有文件路徑的列表。
對(duì)map()?的調(diào)用使用 os.path.getsize()? 來獲得每個(gè)文件的大小。最后使用 reduce()? 和operator.add() 來獲得每個(gè)文件大小的累積總和。最后的結(jié)果是主目錄中所有文件的總大小,單位是字節(jié)。
注意:幾年前,谷歌開發(fā)并開始使用他們稱之為MapReduce的編程模型。這是一種新的數(shù)據(jù)處理方式,旨在使用集群上的并行和分布式計(jì)算來管理大數(shù)據(jù)。
這個(gè)模型的靈感來自于函數(shù)式編程中常用的map和reduce操作的結(jié)合。
MapReduce模型對(duì)谷歌在合理時(shí)間內(nèi)處理海量數(shù)據(jù)的能力產(chǎn)生了巨大影響。然而,到了2014年,谷歌不再使用MapReduce作為他們的主要處理模式。
現(xiàn)在,我們可以找到一些MapReduce的替代實(shí)現(xiàn),如 Apache Hadoop,它是一個(gè)使用MapReduce模型的開源軟件工具的集合。
盡管可以使用 reduce()? 來解決本節(jié)所涉及的問題,但 Python 提供了其他的工具,這些工具可以導(dǎo)致一個(gè)更加 Pythonic 和高效的解決方案。例如可以使用內(nèi)置的函數(shù) sum() 來計(jì)算主目錄中的文件的總大小。
>>> import os
>>> import os.path
>>> files = os.listdir(os.path.expanduser("~"))
>>> sum(map(os.path.getsize, files))
4377381
這個(gè)例子比我們之前看到的例子可讀性和效率都要高很多。
用 starmap() 處理基于元組的可迭代對(duì)象
Python的 itertools.starmap() 生成一個(gè)迭代器,該迭代器將函數(shù)應(yīng)用于從元組可迭代對(duì)象獲得的參數(shù),并產(chǎn)生結(jié)果。當(dāng)處理已經(jīng)分組在元組中的可迭代對(duì)象時(shí),它很有用。
map()? 和 starmap()? 之間的主要區(qū)別在于后者使用解包操作符( *? )調(diào)用其轉(zhuǎn)換函數(shù),將每個(gè)元組參數(shù)解包為幾個(gè)位置參數(shù)。因此,轉(zhuǎn)換函數(shù)被稱為 function(*args)? 而不是function(arg1, arg2,... argN)。
starmap()的官方文檔[1]說,該函數(shù)大致等同于下面的Python函數(shù)。
def starmap(function, iterable):
for args in iterable:
yield function(*args)
這個(gè)函數(shù)中的for循環(huán)對(duì)iterable中的項(xiàng)目進(jìn)行迭代,并產(chǎn)生轉(zhuǎn)換后的項(xiàng)目作為結(jié)果。對(duì)function(*args)的調(diào)用使用了解包操作符,將圖元解包為幾個(gè)位置參數(shù)。下面是一些關(guān)于starmap()如何工作的例子。
這個(gè)函數(shù)中的for? 循環(huán)遍歷iterable?中的元素,并得到轉(zhuǎn)換后的結(jié)果。調(diào)用function(*args)?使用解包操作符將元組解包為幾個(gè)位置參數(shù)。下面是一些關(guān)于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 "", line 1, in
list(starmap(ord, [(2, 7), (4, 3)]))
TypeError: ord() takes exactly one argument (2 given)
在第一個(gè)例子中,使用pow()?來計(jì)算每個(gè)元組中第一個(gè)值對(duì)第二個(gè)值的升冪。這些元組的形式是(基數(shù), 指數(shù))。
如果可迭代對(duì)象中的每個(gè)元組都有兩個(gè)元素,那么 function?也必須接受兩個(gè)參數(shù)。如果元組有三個(gè)元素,那么 function?必須接受三個(gè)參數(shù),依此類推。否則會(huì)得到一個(gè)TypeError
如果使用 map()? 而不是 starmap()?,那么會(huì)得到一個(gè)不同的結(jié)果,因?yàn)?nbsp;map() 從每個(gè)元組中抽取一個(gè)項(xiàng)目。
>>> list(map(pow, (2, 7), (4, 3)))
[16, 343]
注意,map()? 需要兩個(gè)元組,而不是一個(gè)元組的列表。map()? 在每次迭代中也從每個(gè)元組中獲取一個(gè)值。要使 map()? 返回與 starmap() 相同的結(jié)果,需要交換值。
>>> list(map(pow, (2, 4), (7, 3)))
[128, 64]
在這種情況下,我們有兩個(gè)元組而不是一個(gè)元組的列表,還交換了7和4?,F(xiàn)在,第一個(gè)元組提供基數(shù),第二個(gè)元組提供指數(shù)。
用Pythonic風(fēng)格編碼取代map()
像 map()、filter()? 和 reduce() 這樣的函數(shù)式編程工具已經(jīng)存在很長(zhǎng)時(shí)間了。然而,列表推導(dǎo)式和生成器表達(dá)式幾乎在每個(gè)用例中都成為了它們的自然替代品。
例如,map() 提供的功能幾乎總是用一個(gè)列表推導(dǎo)式或生成器表達(dá)式來表達(dá)更好。在下面兩節(jié)中,我們將學(xué)習(xí)如何用列表推導(dǎo)式或生成器表達(dá)式來替換對(duì)map()的調(diào)用,使我們的代碼更有可讀性和Pythonic。
使用列表推導(dǎo)式
有一個(gè)一般的模式,我們可以用一個(gè)列表推導(dǎo)式來代替對(duì)map()的調(diào)用。具體方法如下。
# 用map生成一個(gè)列表
list(map(function, iterable))
# 用list comprehension生成一個(gè)列表
[function(x) for x in iterable]。
注意,列表推導(dǎo)幾乎總是比調(diào)用map()?讀起來更清楚。由于列表推導(dǎo)式在Python開發(fā)人員中非常流行,所以在任何地方都可以找到它們。因此,用列表推導(dǎo)式替換 map() 調(diào)用會(huì)讓其他Python開發(fā)人員更熟悉你的代碼。
這里有一個(gè)例子,說明如何用一個(gè)列表推導(dǎo)式來代替map(),建立一個(gè)平方數(shù)的列表。
>>> # 變換函數(shù)
>>> def square(number):
... return number ** 2
>>> numbers = [1, 2, 3, 4, 5, 6]
>>> # 使用map()
>>> list(map(square, numbers))
[1, 4, 9, 16, 25, 36]
>>> # 使用列表理解
>>> [square(x) for x in numbers]
[1, 4, 9, 16, 25, 36]
如果我們比較這兩種解決方案,那么我們可能會(huì)說,使用列表理解的那個(gè)方案更有可讀性,因?yàn)樗x起來幾乎像純英語(yǔ)。另外,列表理解避免了在map()上明確調(diào)用list()來建立最終的列表。
使用生成器表達(dá)式
map()返回一個(gè)map對(duì)象,它是一個(gè)迭代器,可以按需產(chǎn)生項(xiàng)目。因此,map()的自然替代物是一個(gè)生成器表達(dá)式[2],因?yàn)樯善鞅磉_(dá)式返回生成器對(duì)象,而生成器對(duì)象也是按需產(chǎn)生項(xiàng)目的迭代器。
map()?返回一個(gè)map對(duì)象,這是一個(gè)按需生成項(xiàng)目的迭代器。因此對(duì)map()的自然替換是一個(gè)生成器表達(dá)式,因?yàn)樯善鞅磉_(dá)式返回生成器對(duì)象,這些對(duì)象也是生成按需項(xiàng)的迭代器。
眾所周知,Python迭代器在內(nèi)存消耗方面是非常高效的。這就是為什么map()現(xiàn)在返回一個(gè)迭代器而不是一個(gè)列表的原因。
列表推導(dǎo)式和生成器表達(dá)式之間有一個(gè)微小的語(yǔ)法差異。第一種方法使用一對(duì)方括號(hào)('[]'?)來分隔表達(dá)式。第二個(gè)使用一對(duì)圓括號(hào)('()')。因此,要將列表推導(dǎo)式轉(zhuǎn)換為生成器表達(dá)式,只需將方括號(hào)替換為圓括號(hào)。
可以使用生成器表達(dá)式來編寫代碼,它比使用map()的代碼讀起來更清晰。請(qǐng)看下面的例子。
>>> # 變換函數(shù)
>>> def square(number):
... return number ** 2
>>> numbers = [1, 2, 3, 4, 5, 6]
>>> # 使用map()
>>> map_obj = map(square, numbers)
>>> map_obj
這段代碼與上一節(jié)的代碼有一個(gè)主要區(qū)別:把方括號(hào)改為一對(duì)小括號(hào),把列表理解變成生成器表達(dá)式。
生成器表達(dá)式通常用作函數(shù)調(diào)用中的參數(shù)。在這種情況下,不需要使用圓括號(hào)來創(chuàng)建生成器表達(dá)式,因?yàn)橛糜谡{(diào)用函數(shù)的圓括號(hào)還提供了構(gòu)建生成器的語(yǔ)法。有了這個(gè)想法,通過像這樣調(diào)用 list(),你可以得到與上面例子相同的結(jié)果:
>>> list(square(x) for x in numbers)
[1, 4, 9, 16, 25, 36]
如果在函數(shù)調(diào)用中使用生成器表達(dá)式作為參數(shù),那么就不需要額外的一對(duì)小括號(hào)。我們用來調(diào)用函數(shù)的小括號(hào)提供了構(gòu)建生成器的語(yǔ)法。
在內(nèi)存消耗方面,生成器表達(dá)式與 map() 一樣高效,因?yàn)樗鼈兌挤祷匕葱枭身?xiàng)的迭代器。然而,生成器表達(dá)式幾乎總是會(huì)提高代碼的可讀性。它們還使您的代碼在其他Python開發(fā)人員眼中更像Python。
總結(jié)
我們可以使用Python的 map()? 對(duì)可迭代對(duì)象執(zhí)行映射操作。映射操作包括對(duì)可迭代對(duì)象中的元素應(yīng)用轉(zhuǎn)換函數(shù)來生成轉(zhuǎn)換后的可迭代對(duì)象。通常,map()可以處理和轉(zhuǎn)換可迭代對(duì)象,而無需使用顯式循環(huán)。
在本文中,我們已經(jīng)學(xué)習(xí)了map()?如何工作以及如何使用它來處理可迭代對(duì)象。還了解了一些可以在代碼中替換map()的python工具。
至此我們現(xiàn)在知道如何:
- 使用Python的map()
- 使用map()來處理和轉(zhuǎn)換可迭代對(duì)象,而不使用顯式循環(huán)
- 將map()? 與 filter()? 和 reduce()等函數(shù)組合在一起,以執(zhí)行復(fù)雜的轉(zhuǎn)換
- 用列表推導(dǎo)式和生成器表達(dá)式等工具替換 map()
有了這些新知識(shí),將能夠在代碼中使用map()?,并以函數(shù)式編程風(fēng)格處理代碼。通過將map()替換為列表推導(dǎo)式或生成器表達(dá)式,還可以切換到更python化和更現(xiàn)代的風(fēng)格。
參考資料
[1]starmap的官方文檔: https://docs.python.org/3/library/itertools.html#itertools.starmap
[2]生成器表達(dá)式: https://realpython.com/introduction-to-python-generators/#building-generators-with-generator-expressions
分享名稱:Map函數(shù)的隊(duì)友與對(duì)手
本文鏈接:http://www.5511xx.com/article/dhodggg.html


咨詢
建站咨詢
