Python 中的 Map 函數(shù)處理可迭代對象
?本文中,我們將一起學(xué)習(xí)Python的 map() 如何工作;如何使用 map() 轉(zhuǎn)換不同類型的 Python 迭代變量。
有了這些知識,我們就能在程序中有效地使用map(),或者使用列表推導(dǎo)式或生成器表達式來使代碼更加Pythonic。
為了更好地理解map(),需要一些前置知識,如了解如何使用迭代器、for循環(huán)、函數(shù)和lambda函數(shù)的一些知識。
可以查看我們?nèi)涨霸敿毥榻B過的戳??:Python 中的萬能之王 Lambda 函數(shù)
Python中的函數(shù)式編程
在函數(shù)式編程中,計算是通過組合函數(shù)來完成的,這些函數(shù)接受參數(shù)并返回一個(或多個)具體數(shù)值作為結(jié)果。這些函數(shù)不修改其輸入?yún)?shù),也不改變程序的狀態(tài)。它們只是提供一個特定的計算結(jié)果。這些類型的函數(shù)通常被稱為純函數(shù)。
從理論上講,使用函數(shù)式編程風(fēng)格構(gòu)建的程序?qū)⒏走\行。
- 開發(fā),因為我們可以單獨編碼和使用每一個函數(shù)
- 調(diào)試和測試,因為我們可以測試和調(diào)試個別功能,而不看程序的其他部分。
- 理解,因為我們不需要處理整個程序的狀態(tài)變化
函數(shù)式編程通常使用列表、數(shù)組和其他可迭代的數(shù)據(jù)類型,以及一組對數(shù)據(jù)進行操作和轉(zhuǎn)換的函數(shù)來表示。當(dāng)涉及到用函數(shù)式處理數(shù)據(jù)時,至少有三種常用的技術(shù)。
Mapping 包括對可迭代對象應(yīng)用轉(zhuǎn)換函數(shù)來生成新的可迭代對象。通過對原始可迭代對象中的每一項調(diào)用轉(zhuǎn)換函數(shù)來生成新的可迭代對象中的項。
Filtering 包括對可迭代對象應(yīng)用謂詞或布爾值函數(shù)來生成新的可迭代對象。新可迭代對象中的項是通過過濾掉原始可迭代對象中使謂詞函數(shù)返回False的任何項來生成的。
Reducing 括對可迭代對象應(yīng)用reduce函數(shù)來產(chǎn)生單個累積值。
早在1993年,Python社區(qū)就要求有一些函數(shù)式編程的特性,他們要求的是
- 一個Lambda 匿名函數(shù)
- 一個map()函數(shù)
- 一個filter()函數(shù)
- 一個reduce()函數(shù)
由于一位社區(qū)成員的貢獻,這些函數(shù)式特性被添加到語言中?,F(xiàn)在,map()、filter()和reduce()是Python中函數(shù)式編程風(fēng)格的基本組成部分。
在本文中,我們將了解這些函數(shù)之一,即內(nèi)置函數(shù)map()?。我們還將學(xué)習(xí)如何使用列表推導(dǎo)式和生成器表達式,以Pythonic和可讀的方式獲得map()的相同功能。
開始使用 map() 函數(shù)
有時我們會面臨這樣的情況:需要對輸入可迭代對象的所有項執(zhí)行相同的操作來構(gòu)建一個新的可迭代對象。解決這個問題的最快速、最常見的方法是使用一個 Python for 循環(huán)。然而也可以通過使用 map() 來解決這個問題,而不需要顯式循環(huán)。
在下面,我們將一起學(xué)習(xí) map() 的工作原理,以及如何使用它來處理和轉(zhuǎn)換迭代變量而不需要循環(huán)。
入門 map()
map()循環(huán)遍歷輸入可迭代對象(或可迭代對象)的項,并返回一個迭代器,該迭代器是對原始輸入可迭代對象中的每個項應(yīng)用轉(zhuǎn)換函數(shù)得到的結(jié)果。
根據(jù) 文檔[1],map()接受一個函數(shù)對象和一個可迭代對象(或多個可迭代對象)作為參數(shù),并返回一個迭代器,根據(jù)需要生成轉(zhuǎn)換后的條目。函數(shù)簽名定義如下:
map()?將函數(shù)應(yīng)用于循環(huán)中可迭代對象中的每個元素,并返回一個新的迭代器,按需生成轉(zhuǎn)換后的元素。函數(shù)可以是任何Python函數(shù),其參數(shù)的數(shù)量等于傳遞給 map() 的可迭代對象的數(shù)量。
注意: map() 的第一個參數(shù)是一個函數(shù)對象,需要傳遞一個函數(shù)而不調(diào)用它。即不需要使用一對圓括號。
map()的這個第一個參數(shù)是一個轉(zhuǎn)換函數(shù)。換句話說,它是將每個原始項轉(zhuǎn)化為新的 (已轉(zhuǎn)化) 項的函數(shù)。盡管 Python 文檔中稱這個參數(shù)為函數(shù),但它可以是任何 Python 可調(diào)用的函數(shù),這包括內(nèi)置函數(shù)、類、方法、lambda 函數(shù)和用戶定義的函數(shù)。
map()?執(zhí)行的操作通常被稱為mapping,因為它將輸入可迭代對象中的每一項映射到結(jié)果可迭代對象中的新項。為此,map()對輸入的可迭代對象中的所有項應(yīng)用一個轉(zhuǎn)換函數(shù)。
為了更好地理解map(),假設(shè)需要取一個數(shù)值列表,并將其轉(zhuǎn)化為一個包含原列表中每個數(shù)字的平方值的列表。此時可以使用一個for循環(huán),代碼如下。
當(dāng)在 numbers?上運行這個循環(huán)時,會得到一個平方值列表。for? 循環(huán)遍歷 numbers?,并對每個值應(yīng)用冪運算。最后,它將結(jié)果值存儲在squared中。
使用map()可以在不使用顯式循環(huán)的情況下獲得相同的結(jié)果??纯瓷厦媸纠囊韵轮匦聦崿F(xiàn):
square()?是一個轉(zhuǎn)換函數(shù),將一個數(shù)字映射到它的平方值。調(diào)用map()將square()?應(yīng)用于numbers?中的所有值,并返回一個生成平方值的迭代器。然后在map()?上調(diào)用 list() 來創(chuàng)建一個包含平方值的列表對象。
由于map()?是用C語言編寫的,并且是高度優(yōu)化的,它的內(nèi)部隱含循環(huán)可以比普通的Python for循環(huán)更有效率。這是使用map()的一個優(yōu)點。
map()?的第二個優(yōu)點與內(nèi)存消耗有關(guān)。使用for循環(huán)時,需要將整個列表存儲在系統(tǒng)的內(nèi)存中。使用map(),可以按需獲得項目,并且在給定的時間內(nèi)只有一個項目在系統(tǒng)的內(nèi)存中。
注意: 在Python中2。X,map()? 返回一個列表。此行為在Python 3.x中發(fā)生了改變?,F(xiàn)在,map()?返回一個map對象,這是一個迭代器,可生成按需的項目。這就是為什么我們需要調(diào)用 list() 來創(chuàng)建想要的列表對象。
再比如,我們需要將一個列表中的所有項目從字符串轉(zhuǎn)換為整數(shù)。要做到這一點,我們可以使用map()和int(),如下所示。
map()? 將 int()? 應(yīng)用于 str_nums 中的每個值。由于 map()? 返回一個迭代器(一個map對象),需要調(diào)用 list(),這樣我們就可以用完迭代器并將其變成一個list對象。注意,在這個過程中,原始序列不會被修改。
在不同類型的函數(shù)中使用map()
我們可以用 map()? 來使用任何種類的Python可調(diào)用函數(shù)。唯一的條件是該可調(diào)用函數(shù)需要一個參數(shù),并返回一個具體而有用的值。例如,我們可以使用類、實現(xiàn)了特殊方法 __call__()的實例、實例方法、類方法、靜態(tài)方法以及函數(shù)。
有一些內(nèi)置的函數(shù),我們可以和 map() 一起使用。請看下面的例子。
我們可以用 map() 使用任何內(nèi)置函數(shù),只要該函數(shù)接受一個參數(shù)并返回一個值。
在使用map()?時,我們會看到一個常見的模式是使用一個lambda函數(shù)作為第一個參數(shù)。當(dāng)我們需要向map()傳遞一個基于表達式的函數(shù)時,lambda函數(shù)就很方便。例如,我們可以用lambda函數(shù)重新實現(xiàn)平方值的例子,如下所示。
在使用 map()? 時,lambda函數(shù)是相當(dāng)有用的。它們可以發(fā)揮 map() ?的第一個參數(shù)的作用。我們可以在使用 map() 的同時使用lambda函數(shù)來快速處理和轉(zhuǎn)換我們的迭代變量。
用map()處理多個輸入可迭代對象
如果為map()?提供了多個迭代變量,那么轉(zhuǎn)換函數(shù)必須接受與傳遞的迭代變量同樣多的參數(shù)。map()的每一次迭代將從每個迭代器中傳遞一個值作為參數(shù)給函數(shù),迭代在最短的迭代器的末端停止。
以下使用 pow() 的例子。
pow()? 接收兩個參數(shù),x和y?,并返回x到y(tǒng)?的冪。在第一次迭代中,x是1,y是4?,結(jié)果是1?。在第二次迭代中,x是2,y是5?,結(jié)果是32?,以此類推。最后的可迭代的長度只相當(dāng)于最短的可迭代的長度,在這種情況下就是first_it。
我們可以使用不同種類的數(shù)學(xué)運算來合并兩個或多個數(shù)值迭代表。下面是一些使用lambda函數(shù)對幾個輸入迭代表進行不同數(shù)學(xué)運算的例子。
在第一個示例中,使用減法操作合并兩個可迭代對象,每個可迭代對象包含三個元素。在第二個示例中,將三個可迭代對象的值相加。
用map()轉(zhuǎn)換字符串的可迭代對象
當(dāng)處理字符串對象的可迭代對象時,我們可以借助Python的map()?,使用某種轉(zhuǎn)換函數(shù)轉(zhuǎn)換所有對象。下面我們通過一些示例,了解如何使用 map()轉(zhuǎn)換字符串對象的可迭代對象。
使用 str?的方法
處理字符串的一個很常見的方法是使用str?類的一些方法將給定的字符串轉(zhuǎn)換為一個新的字符串。如果我們正在處理字符串的迭代表,并且需要對每個字符串應(yīng)用相同的轉(zhuǎn)換,那么我們可以使用 map() 和各種字符串方法。
使用map() 和str?方法,可以對string_it ?中的每一項執(zhí)行一些轉(zhuǎn)換。大多數(shù)時候,使用不帶附加參數(shù)的方法,比如str.capitalize()?, str.lower()?, str.swapcase()?, str.title()?,以及str.upper()。
我們也可以使用一些帶有默認值的附加參數(shù)的方法,比如str.strip()?,它需要一個叫做char的可選參數(shù),默認為刪除空白。
當(dāng)像這樣使用 str.strip()? 時,其實是傳入默認值char?。在這種情況下,我們使用 map()? 來刪除 with_spaces 項中的所有空白。
注意: 如果需要提供參數(shù)而不是使用默認值,可以使用lambda函數(shù)。
下面是一個使用 str.strip() 去除點而不是默認的空白的例子。
lambda?函數(shù)在字符串對象s?上調(diào)用.strip(),刪除所有前導(dǎo)和尾部的點。
例如,當(dāng)處理文本文件時,其中的行可能有尾部的空格(或其他字符),而需要刪除它們時,這種技術(shù)就會很方便。如果是這種情況,那么需要考慮使用str.strip()而不使用自定義字符,也會刪除換行字符。
刪除標點符號
有時在處理文本時,需要刪除將文本分割成單詞后留下的標點符號。我們可以創(chuàng)建一個自定義函數(shù),使用匹配最常見的標點符號的正則表達式來刪除單個單詞的標點符號。
下面是使用sub()[2]實現(xiàn)該函數(shù)的可能方法,它是一個存在于Python標準庫中的re[3]模塊中的正則表達式函數(shù):
在自定義函數(shù)remove_punctuation()?中,使用一個正則表達式模式來匹配在任何英文文本中發(fā)現(xiàn)的最常見的標點符號。對re.sub()?的調(diào)用使用一個空字符串("")替換了匹配的標點符號,并返回一個干凈的單詞。
有了轉(zhuǎn)換函數(shù),我們可以使用map()對文本中的每個字進行轉(zhuǎn)換。下面是它的工作原理。
在這段文字中,有些詞包括標點符號。例如,我們有 'people,'?而不是 'people','problem,'?不是 'problem'?,等等。對map()?的調(diào)用將remove_punctuation()應(yīng)用于每個詞,并刪除任何標點符號。因此,第二個列表中存儲的是處理后的干凈的單詞。
注意我們的正則表達式中沒有撇號('?),因為我們想將像I'll這樣的縮略語保持原貌。
實現(xiàn)凱撒密碼算法
羅馬政治家Julius Caesar?曾經(jīng)用密碼來保護他發(fā)給他的將軍們的信息,用密碼來加密。Caesar cipher 凱撒密碼[4]將每個字母移位若干個字母。例如,如果我們將字母a?移位三個,那么我們就會得到字母d,以此類推。
如果移位超過了字母表的末端,那么我們只需要旋轉(zhuǎn)回字母表的開頭。在旋轉(zhuǎn)3的情況下,x?會變成a。以下是旋轉(zhuǎn)后字母表的樣子。
- 原文字母: abcdefghijklmnopqrstuvwxyz
- 字母表旋轉(zhuǎn)3: defghijklmnopqrstuvwxyzabc
下面的代碼實現(xiàn)了rotate_chr()?,這個函數(shù)接收一個字符并將其旋轉(zhuǎn)3圈。rotate_chr()將返回旋轉(zhuǎn)后的字符。下面是代碼。
在rotate_chr()中,首先檢查該字符是否在字母表中。如果不是,那么就返回相同的字符。這樣做的目的是為了保留標點符號和其他不尋常的字符。在第8行,計算該字符在字母表中的新的旋轉(zhuǎn)位置,這里要使用內(nèi)置函數(shù)ord()[5]。
ord()?接收一個Unicode字符,并返回一個整數(shù),代表輸入字符的 Unicode碼位。例如,ord("a")?返回97?,而ord("b")?返回98。
ord() 接收一個字符作為參數(shù),并返回輸入字符的Unicode碼位。
如果把這個整數(shù)加到rot_by?的目標數(shù)上,那么將得到新字母在字母表中的旋轉(zhuǎn)位置。在這個例子中,rot_by?是3。因此,字母 "a"? 旋轉(zhuǎn)3下將成為位置100的字母,也就是字母 "d"?。字母 "b"? 旋轉(zhuǎn)三下將成為位置101?的字母,也就是字母 "e",以此類推。
如果字母的新位置不超過最后一個字母的位置(alphabet[-1]?),那么就返回這個新位置的字母,此時可以使用內(nèi)置函數(shù)chr()。
chr()是ord()?的逆運算。它接收一個代表Unicode字符的Unicode碼位的整數(shù),并返回該位置的字符。例如,chr(97)?將返回'a'?,而chr(98)?將返回'b'。
chr()接收一個整數(shù),代表一個字符的Unicode碼位,并返回相應(yīng)的字符。
最后,如果新的旋轉(zhuǎn)位置超出了最后一個字母的位置(alphabet[-1]?),那么需要旋轉(zhuǎn)回到字母表的開頭,此時需要從旋轉(zhuǎn)的位置減去字母表的長度(rotated_pos - len(alphabet)?),然后用chr()返回這個新位置的字母。
用rotate_chr()?作為轉(zhuǎn)換函數(shù),可以用map()?用凱撒密碼算法對任何文本進行加密。下面是一個使用str.join()來連接字符串的例子。
字符串在Python中也是可迭代的。因此,對map()?的調(diào)用將rotate_chr()?應(yīng)用于原始輸入字符串中的每個字符。在這種情況下,"M"? 變成 "p","y"? 變成 "b"?,等等。最后,對str.join()的調(diào)用將每個旋轉(zhuǎn)的字符連接到一個最終的加密信息中。
用map()轉(zhuǎn)換數(shù)字的可迭代對象
map()在處理和轉(zhuǎn)換數(shù)字值的迭代表方面也有很大潛力??梢赃M行各種各樣的數(shù)學(xué)和算術(shù)運算,將字符串值轉(zhuǎn)換成浮點數(shù)或整數(shù),等等。
在下面的內(nèi)容中,我們將一起學(xué)習(xí)一些如何使用map()來處理和轉(zhuǎn)換數(shù)字迭代表的例子。
使用數(shù)學(xué)運算
使用數(shù)學(xué)運算來轉(zhuǎn)換數(shù)值迭代的一個常見例子是使用冪運算符(**)。在下面的例子中,我們編寫了一個轉(zhuǎn)換函數(shù),它接收一個數(shù)字并返回數(shù)字的平方和立方。
powers()?接受一個數(shù)字'x'?并返回它的平方和立方。由于Python將多個返回值作為元組處理,所以每次調(diào)用powers()?都會返回一個有兩個值的元組。當(dāng)你用 powers()? 作為參數(shù)調(diào)用 map()時,你會得到一個包含輸入可迭代對象中每個數(shù)字的平方和立方的元組列表。
使用 map()? 可以執(zhí)行許多與數(shù)學(xué)相關(guān)的轉(zhuǎn)換。您可以在每個值上添加常量或從它們中減去常量。您還可以使用 math 模塊中的一些函數(shù),如sqrt() , factorial()? , sin(),cos() ?,等等。下面是一個使用 factorial() 的例子:
在這種情況下,我們將數(shù)字轉(zhuǎn)化為一個新的列表,包含原列表中每個數(shù)字的階乘。
我們可以使用'map()對數(shù)字可迭代對象執(zhí)行廣泛的數(shù)學(xué)轉(zhuǎn)換。你深入到這個話題的程度取決于你的需求和你的想象力。考慮一下這個問題,編寫我們自己的示例代碼!
轉(zhuǎn)換溫度?
·的另一個用例是在測量單位之間進行轉(zhuǎn)換。假設(shè)我們有一個以攝氏度或華氏度測量的溫度列表,我們需要將它們轉(zhuǎn)換為相應(yīng)的華氏度或攝氏度的溫度。
可以編碼兩個轉(zhuǎn)換函數(shù)來完成這個任務(wù)。
to_fahrenheit()? 接收攝氏溫度測量值,并將其轉(zhuǎn)換為華氏溫度。類似地,to_celsius()接收華氏溫度并將其轉(zhuǎn)換為攝氏溫度。
這些函數(shù)可以作為轉(zhuǎn)換函數(shù)使用,即可以將它們與map()一起使用,將溫度測量值的可迭代數(shù)據(jù)分別轉(zhuǎn)換為華氏和攝氏。
如果用to_fahrenheit()和celsius_temps?調(diào)用map()?,那么會得到一個華氏溫度測量值的列表。如果用to_celsius()和fahr_temps?來調(diào)用map(),那么會得到一個以攝氏度為單位的溫度測量列表。
要擴展這個例子并涵蓋任何其他種類的單位轉(zhuǎn)換,只需要編碼一個適當(dāng)?shù)霓D(zhuǎn)換函數(shù)。
將字符串轉(zhuǎn)換為數(shù)字
在處理數(shù)字數(shù)據(jù)時,我們可能會遇到所有數(shù)據(jù)都是字符串值的情況。要做任何進一步的計算,我們需要將字符串值轉(zhuǎn)換成數(shù)字值。map()也可以幫助處理這些情況。
如果我們確定我們的數(shù)據(jù)是干凈的,不包含錯誤的值,那么我們可以根據(jù)我們的需要直接使用float()或int()。下面是一些例子。
在第一個例子中,使用float()與map()?將所有的值從字符串值轉(zhuǎn)換為浮點值。在第二個例子中,使用int()將字符串轉(zhuǎn)換為整數(shù)。注意,如果其中一個值不是一個有效的數(shù)字,那么會得到一個ValueError。
如果我們不確定數(shù)據(jù)是否干凈,那么可以使用一個更精細的轉(zhuǎn)換函數(shù),如下面的。
在to_float()?中,使用try語句?,在轉(zhuǎn)換number? 時,如果 float()? 失敗,則捕獲 ValueError 。如果沒有發(fā)生錯誤,那么函數(shù)將返回轉(zhuǎn)換為有效浮點數(shù)的number? 。否則,將得到nan? (非數(shù)值) 值,這是一個特殊的 float 值,可以使用它來表示不是有效數(shù)字的值,就像上面示例中的 "One" 一樣。
我們可以根據(jù)需要定制 to_float()?。例如,我們可以用 return 0.0? 語句代替 return float("nan") 語句,等等。
在后續(xù)的文章中,我們將繼續(xù)學(xué)習(xí)如何將 map() 與其他函數(shù)工具 結(jié)合起來,并且為了使代碼更加Pythonic,使用列表推導(dǎo)式和生成器表達式來 替代 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