Python 提供了大量的內(nèi)置數(shù)據(jù)結(jié)構(gòu),包括列表,集合以及字典。大多數(shù)情況下使用這些數(shù)據(jù)結(jié)構(gòu)是很簡(jiǎn)單的。但是,我們也會(huì)經(jīng)常碰到到諸如查詢,排序和過(guò)濾等等這些普遍存在的問(wèn)題。因此,這一章的目的就是討論這些比較常見(jiàn)的問(wèn)題和算法。另外,我們也會(huì)給出在集合模塊 collections
當(dāng)中操作這些數(shù)據(jù)結(jié)構(gòu)的方法。
現(xiàn)在有一個(gè)包含 N 個(gè)元素的元組或者是序列,怎樣將它里面的值解壓后同時(shí)賦值給 N 個(gè)變量?
任何的序列(或者是可迭代對(duì)象)可以通過(guò)一個(gè)簡(jiǎn)單的賦值語(yǔ)句解壓并賦值給多個(gè)變量。唯一的前提就是變量的數(shù)量必須跟序列元素的數(shù)量是一樣的。
代碼示例:
>>> p = (4, 5)
>>> x, y = p
>>> x
4
>>> y
5
>>>
>>> data = [ 'ACME', 50, 91.1, (2012, 12, 21) ]
>>> name, shares, price, date = data
>>> name
'ACME'
>>> date
(2012, 12, 21)
>>> name, shares, price, (year, mon, day) = data
>>> name
'ACME'
>>> year
2012
>>> mon
12
>>> day
21
>>>
如果變量個(gè)數(shù)和序列元素的個(gè)數(shù)不匹配,會(huì)產(chǎn)生一個(gè)異常。
代碼示例:
>>> p = (4, 5)
>>> x, y, z = p
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: need more than 2 values to unpack
>>>
實(shí)際上,這種解壓賦值可以用在任何可迭代對(duì)象上面,而不僅僅是列表或者元組。包括字符串,文件對(duì)象,迭代器和生成器。
代碼示例:
>>> s = 'Hello'
>>> a, b, c, d, e = s
>>> a
'H'
>>> b
'e'
>>> e
'o'
>>>
有時(shí)候,你可能只想解壓一部分,丟棄其他的值。對(duì)于這種情況 Python 并沒(méi)有提供特殊的語(yǔ)法。但是你可以使用任意變量名去占位,到時(shí)候丟掉這些變量就行了。
代碼示例:
>>> data = [ 'ACME', 50, 91.1, (2012, 12, 21) ]
>>> _, shares, price, _ = data
>>> shares
50
>>> price
91.1
>>>
你必須保證你選用的那些占位變量名在其他地方?jīng)]被使用到。
如果一個(gè)可迭代對(duì)象的元素個(gè)數(shù)超過(guò)變量個(gè)數(shù)時(shí),會(huì)拋出一個(gè) ValueError
。那么怎樣才能從這個(gè)可迭代對(duì)象中解壓出 N 個(gè)元素出來(lái)?
Python 的星號(hào)表達(dá)式可以用來(lái)解決這個(gè)問(wèn)題。比如,你在學(xué)習(xí)一門課程,在學(xué)期末的時(shí)候,你想統(tǒng)計(jì)下家庭作業(yè)的平均成績(jī),但是排除掉第一個(gè)和最后一個(gè)分?jǐn)?shù)。如果只有四個(gè)分?jǐn)?shù),你可能就直接去簡(jiǎn)單的手動(dòng)賦值,但如果有24個(gè)呢?這時(shí)候星號(hào)表達(dá)式就派上用場(chǎng)了:
def drop_first_last(grades):
first, *middle, last = grades
return avg(middle)
另外一種情況,假設(shè)你現(xiàn)在有一些用戶的記錄列表,每條記錄包含一個(gè)名字、郵件,接著就是不確定數(shù)量的電話號(hào)碼。你可以像下面這樣分解這些記錄:
>>> record = ('Dave', 'dave@example.com', '773-555-1212', '847-555-1212')
>>> name, email, *phone_numbers = record
>>> name
'Dave'
>>> email
'dave@example.com'
>>> phone_numbers
['773-555-1212', '847-555-1212']
>>>
值得注意的是上面解壓出的 phone_numbers
變量永遠(yuǎn)都是列表類型,不管解壓的電話號(hào)碼數(shù)量是多少(包括0個(gè))。所以,任何使用到 phone_numbers
變量的代碼就不需要做多余的類型檢查去確認(rèn)它是否是列表類型了。
星號(hào)表達(dá)式也能用在列表的開(kāi)始部分。比如,你有一個(gè)公司前8個(gè)月銷售數(shù)據(jù)的序列,但是你想看下最近一個(gè)月數(shù)據(jù)和前面7個(gè)月的平均值的對(duì)比。你可以這樣做:
*trailing_qtrs, current_qtr = sales_record
trailing_avg = sum(trailing_qtrs) / len(trailing_qtrs)
return avg_comparison(trailing_avg, current_qtr)
下面是在 Python 解釋器中執(zhí)行的結(jié)果:
>>> *trailing, current = [10, 8, 7, 1, 9, 5, 10, 3]
>>> trailing
[10, 8, 7, 1, 9, 5, 10]
>>> current
3
擴(kuò)展的迭代解壓語(yǔ)法是專門為解壓不確定個(gè)數(shù)或任意個(gè)數(shù)元素的可迭代對(duì)象而設(shè)計(jì)的。通常,這些可迭代對(duì)象的元素結(jié)構(gòu)有確定的規(guī)則(比如第1個(gè)元素后面都是電話號(hào)碼),星號(hào)表達(dá)式讓開(kāi)發(fā)人員可以很容易的利用這些規(guī)則來(lái)解壓出元素來(lái)。而不是通過(guò)一些比較復(fù)雜的手段去獲取這些關(guān)聯(lián)的的元素值。
值得注意的是,星號(hào)表達(dá)式在迭代元素為可變長(zhǎng)元組的序列時(shí)是很有用的。比如,下面是一個(gè)帶有標(biāo)簽的元組序列:
records = [
('foo', 1, 2),
('bar', 'hello'),
('foo', 3, 4),
]
def do_foo(x, y):
print('foo', x, y)
def do_bar(s):
print('bar', s)
for tag, *args in records:
if tag == 'foo':
do_foo(*args)
elif tag == 'bar':
do_bar(*args)
星號(hào)解壓語(yǔ)法在字符串操作的時(shí)候也會(huì)很有用,比如字符串的分割。
代碼示例:
>>> line = 'nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false'
>>> uname, *fields, homedir, sh = line.split(':')
>>> uname
'nobody'
>>> homedir
'/var/empty'
>>> sh
'/usr/bin/false'
>>>
有時(shí)候,你想解壓一些元素后丟棄它們,你不能簡(jiǎn)單就使用 *,但是你可以使用一個(gè)普通的廢棄名稱,比如_
或者ign
。
代碼示例:
>>> record = ('ACME', 50, 123.45, (12, 18, 2012))
>>> name, *_, (*_, year) = record
>>> name
'ACME'
>>> year
2012
>>>
在很多函數(shù)式語(yǔ)言中,星號(hào)解壓語(yǔ)法跟列表處理有許多相似之處。比如,如果你有一個(gè)列表,你可以很容易的將它分割成前后兩部分:
>>> items = [1, 10, 7, 4, 5, 9]
>>> head, *tail = items
>>> head
1
>>> tail
[10, 7, 4, 5, 9]
>>>
如果你夠聰明的話,還能用這種分割語(yǔ)法去巧妙的實(shí)現(xiàn)遞歸算法。比如:
>>> def sum(items):
... head, *tail = items
... return head + sum(tail) if tail else head
...
>>> sum(items)
36
>>>
然后,由于語(yǔ)言層面的限制,遞歸并不是 Python 擅長(zhǎng)的。因此,最后那個(gè)遞歸演示僅僅是個(gè)好奇的探索罷了,對(duì)這個(gè)不要太認(rèn)真了。
在迭代操作或者其他操作的時(shí)候,怎樣只保留最后有限幾個(gè)元素的歷史記錄?
保留有限歷史記錄正是 collections.deque
大顯身手的時(shí)候。比如,下面的代碼在多行上面做簡(jiǎn)單的文本匹配,并只返回在前N 行中匹配成功的行:
from collections import deque
def search(lines, pattern, history=5):
previous_lines = deque(maxlen=history)
for li in lines:
if pattern in li:
yield li, previous_lines
previous_lines.append(li)
# Example use on a file
if __name__ == '__main__':
with open(r'../../cookbook/somefile.txt') as f:
for line, prevlines in search(f, 'python', 5):
for pline in prevlines:
print(pline, end='')
print(line, end='')
print('-' * 20)
我們?cè)趯懖樵冊(cè)氐拇a時(shí),通常會(huì)使用包含 yield
表達(dá)式的生成器函數(shù),也就是我們上面示例代碼中的那樣。這樣可以將搜索過(guò)程代碼和使用搜索結(jié)果代碼解耦。如果你還不清楚什么是生成器,請(qǐng)參看4.3節(jié)。
使用 deque(maxlen=N)
構(gòu)造函數(shù)會(huì)新建一個(gè)固定大小的隊(duì)列。當(dāng)新的元素加入并且這個(gè)隊(duì)列已滿的時(shí)候, 最老的元素會(huì)自動(dòng)被移除掉。
代碼示例:
>>> q = deque(maxlen=3)
>>> q.append(1)
>>> q.append(2)
>>> q.append(3)
>>> q
deque([1, 2, 3], maxlen=3)
>>> q.append(4)
>>> q
deque([2, 3, 4], maxlen=3)
>>> q.append(5)
>>> q
deque([3, 4, 5], maxlen=3)
盡管你也可以手動(dòng)在一個(gè)列表上實(shí)現(xiàn)這一的操作(比如增加、刪除等等)。但是這里的隊(duì)列方案會(huì)更加優(yōu)雅并且運(yùn)行得更快些。
更一般的, deque
類可以被用在任何你只需要一個(gè)簡(jiǎn)單隊(duì)列數(shù)據(jù)結(jié)構(gòu)的場(chǎng)合。 如果你不設(shè)置最大隊(duì)列大小,那么就會(huì)得到一個(gè)無(wú)限大小隊(duì)列,你可以在隊(duì)列的兩端執(zhí)行添加和彈出元素的操作。
代碼示例:
>>> q = deque()
>>> q.append(1)
>>> q.append(2)
>>> q.append(3)
>>> q
deque([1, 2, 3])
>>> q.appendleft(4)
>>> q
deque([4, 1, 2, 3])
>>> q.pop()
3
>>> q
deque([4, 1, 2])
>>> q.popleft()
4
在隊(duì)列兩端插入或刪除元素時(shí)間復(fù)雜度都是 O(1)
,而在列表的開(kāi)頭插入或刪除元素的時(shí)間復(fù)雜度為 O(N)
。
怎樣從一個(gè)集合中獲得最大或者最小的 N 個(gè)元素列表?
heapq 模塊有兩個(gè)函數(shù):nlargest()
和 nsmallest()
可以完美解決這個(gè)問(wèn)題。
import heapq
nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
print(heapq.nlargest(3, nums)) # Prints [42, 37, 23]
print(heapq.nsmallest(3, nums)) # Prints [-4, 1, 2]
兩個(gè)函數(shù)都能接受一個(gè)關(guān)鍵字參數(shù),用于更復(fù)雜的數(shù)據(jù)結(jié)構(gòu)中:
portfolio = [
{'name': 'IBM', 'shares': 100, 'price': 91.1},
{'name': 'AAPL', 'shares': 50, 'price': 543.22},
{'name': 'FB', 'shares': 200, 'price': 21.09},
{'name': 'HPQ', 'shares': 35, 'price': 31.75},
{'name': 'YHOO', 'shares': 45, 'price': 16.35},
{'name': 'ACME', 'shares': 75, 'price': 115.65}
]
cheap = heapq.nsmallest(3, portfolio, key=lambda s: s['price'])
expensive = heapq.nlargest(3, portfolio, key=lambda s: s['price'])
譯者注:上面代碼在對(duì)每個(gè)元素進(jìn)行對(duì)比的時(shí)候,會(huì)以 price
的值進(jìn)行比較。
如果你想在一個(gè)集合中查找最小或最大的 N 個(gè)元素,并且 N 小于集合元素?cái)?shù)量,那么這些函數(shù)提供了很好的性能。 因?yàn)樵诘讓訉?shí)現(xiàn)里面,首先會(huì)先將集合數(shù)據(jù)進(jìn)行堆排序后放入一個(gè)列表中:
>>> nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
>>> import heapq
>>> heapq.heapify(nums)
>>> nums
[-4, 2, 1, 23, 7, 2, 18, 23, 42, 37, 8]
>>>
堆數(shù)據(jù)結(jié)構(gòu)最重要的特征是 heap[0]
永遠(yuǎn)是最小的元素。并且剩余的元素可以很容易的通過(guò)調(diào)用 heapq.heappop()
方法得到, 該方法會(huì)先將第一個(gè)元素彈出來(lái),然后用下一個(gè)最小的元素來(lái)取代被彈出元素(這種操作時(shí)間復(fù)雜度僅僅是O(logN),N 是堆大小)。 比如,如果想要查找最小的3個(gè)元素,你可以這樣做:
>>> heapq.heappop(nums)
-4
>>> heapq.heappop(nums)
1
>>> heapq.heappop(nums)
2
當(dāng)要查找的元素個(gè)數(shù)相對(duì)比較小的時(shí)候,函數(shù) nlargest()
和 nsmallest()
是很合適的。如果你僅僅想查找唯一的最小或最大(N=1)的元素的話,那么使用 min()和 max()函數(shù)會(huì)更快些。類似的,如果 N 的大小和集合大小接近的時(shí)候,通常先排序這個(gè)集合然后再使用切片操作會(huì)更快點(diǎn) ( sorted(items)[:N]
或者是 sorted(items)[-N:]
)。需要在正確場(chǎng)合使用函數(shù)nlargest() 和 nsmallest()才能發(fā)揮它們的優(yōu)勢(shì) (如果N 快接近集合大小了,那么使用排序操作會(huì)更好些)。
盡管你沒(méi)有必要一定使用這里的方法,但是堆數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)是一個(gè)很有趣并且值得你深入學(xué)習(xí)的東西。基本上只要是數(shù)據(jù)結(jié)構(gòu)和算法書(shū)籍里面都會(huì)有提及到。heapq
模塊的官方文檔里面也詳細(xì)的介紹了堆數(shù)據(jù)結(jié)構(gòu)底層的實(shí)現(xiàn)細(xì)節(jié)。
怎樣實(shí)現(xiàn)一個(gè)按優(yōu)先級(jí)排序的隊(duì)列? 并且在這個(gè)隊(duì)列上面每次 pop 操作總是返回優(yōu)先級(jí)最高的那個(gè)元素
下面的類利用 heapq
模塊實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的優(yōu)先級(jí)隊(duì)列:
import heapq
class PriorityQueue:
def __init__(self):
self._queue = []
self._index = 0
def push(self, item, priority):
heapq.heappush(self._queue, (-priority, self._index, item))
self._index += 1
def pop(self):
return heapq.heappop(self._queue)[-1]
下面是它的使用方式:
>>> class Item:
... def __init__(self, name):
... self.name = name
... def __repr__(self):
... return 'Item({!r})'.format(self.name)
...
>>> q = PriorityQueue()
>>> q.push(Item('foo'), 1)
>>> q.push(Item('bar'), 5)
>>> q.push(Item('spam'), 4)
>>> q.push(Item('grok'), 1)
>>> q.pop()
Item('bar')
>>> q.pop()
Item('spam')
>>> q.pop()
Item('foo')
>>> q.pop()
Item('grok')
>>>
仔細(xì)觀察可以發(fā)現(xiàn),第一個(gè) pop()
操作返回優(yōu)先級(jí)最高的元素。另外注意到如果兩個(gè)有著相同優(yōu)先級(jí)的元素( foo
和 grok
),pop 操作按照它們被插入到隊(duì)列的順序返回的。
這一小節(jié)我們主要關(guān)注 heapq
模塊的使用。 函數(shù) heapq.heappush()
和 heapq.heappop()
分別在隊(duì)列 _queue
上插入和刪除第一個(gè)元素, 并且隊(duì)列_queue 保證第一個(gè)元素?fù)碛凶钚?yōu)先級(jí)(1.4節(jié)已經(jīng)討論過(guò)這個(gè)問(wèn)題)。 heappop()
函數(shù)總是返回”最小的”的元素,這就是保證隊(duì)列 pop 操作返回正確元素的關(guān)鍵。另外,由于 push 和 pop 操作時(shí)間復(fù)雜度為O(logN),其中 N 是堆的大小,因此就算是 N 很大的時(shí)候它們運(yùn)行速度也依舊很快。
在上面代碼中,隊(duì)列包含了一個(gè) (-priority, index, item
) 的元組。優(yōu)先級(jí)為負(fù)數(shù)的目的是使得元素按照優(yōu)先級(jí)從高到低排序。 這個(gè)跟普通的按優(yōu)先級(jí)從低到高排序的堆排序恰巧相反。
index
變量的作用是保證同等優(yōu)先級(jí)元素的正確排序。通過(guò)保存一個(gè)不斷增加的 index
下標(biāo)變量,可以確保元素按照它們插入的順序排序。而且,index
變量也在相同優(yōu)先級(jí)元素比較的時(shí)候起到重要作用。
為了闡明這些,先假定 Item 實(shí)例是不支持排序的:
>>> a = Item('foo')
>>> b = Item('bar')
>>> a < b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: Item() < Item()
>>>
如果你使用元組 (priority, item)
,只要兩個(gè)元素的優(yōu)先級(jí)不同就能比較。 但是如果兩個(gè)元素優(yōu)先級(jí)一樣的話,那么比較操作就會(huì)跟之前一樣出錯(cuò):
>>> a = (1, Item('foo'))
>>> b = (5, Item('bar'))
>>> a < b
True
>>> c = (1, Item('grok'))
>>> a < c
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: Item() < Item()
>>>
通過(guò)引入另外的 index
變量組成三元組 (priority, index, item)
,就能很好的避免上面的錯(cuò)誤, 因?yàn)椴豢赡苡袃蓚€(gè)元素有相同的 index
值。Python 在做元組比較時(shí)候,如果前面的比較以及可以確定結(jié)果了, 后面的比較操作就不會(huì)發(fā)生了:
>>> a = (1, 0, Item('foo'))
>>> b = (5, 1, Item('bar'))
>>> c = (1, 2, Item('grok'))
>>> a < b
True
>>> a < c
True
>>>
如果你想在多個(gè)線程中使用同一個(gè)隊(duì)列,那么你需要增加適當(dāng)?shù)逆i和信號(hào)量機(jī)制。 可以查看12.3 小節(jié)的例子演示是怎樣做的。
heapq
模塊的官方文檔有更詳細(xì)的例子程序以及對(duì)于堆理論及其實(shí)現(xiàn)的詳細(xì)說(shuō)明。
怎樣實(shí)現(xiàn)一個(gè)鍵對(duì)應(yīng)多個(gè)值的字典(也叫 multidict
)?
一個(gè)字典就是一個(gè)鍵對(duì)應(yīng)一個(gè)單值的映射。如果你想要一個(gè)鍵映射多個(gè)值,那么你就需要將這多個(gè)值放到另外的容器中, 比如列表或者集合里面。比如,你可以像下面這樣構(gòu)造這樣的字典:
d = {
'a' : [1, 2, 3],
'b' : [4, 5]
}
e = {
'a' : {1, 2, 3},
'b' : {4, 5}
}
選擇使用列表還是集合取決于你的實(shí)際需求。如果你想保持元素的插入順序就應(yīng)該使用列表, 如果想去掉重復(fù)元素就使用集合(并且不關(guān)心元素的順序問(wèn)題)。
你可以很方便的使用 collections
模塊中的 defaultdict
來(lái)構(gòu)造這樣的字典。 defaultdict
的一個(gè)特征是它會(huì)自動(dòng)初始化每個(gè) key
剛開(kāi)始對(duì)應(yīng)的值,所以你只需要關(guān)注添加元素操作了。比如:
from collections import defaultdict
d = defaultdict(list)
d['a'].append(1)
d['a'].append(2)
d['b'].append(4)
d = defaultdict(set)
d['a'].add(1)
d['a'].add(2)
d['b'].add(4)
需要注意的是, defaultdict
會(huì)自動(dòng)為將要訪問(wèn)的鍵(就算目前字典中并不存在這樣的鍵)創(chuàng)建映射實(shí)體。 如果你并不需要這樣的特性,你可以在一個(gè)普通的字典上使用 setdefault()
方法來(lái)代替。比如:
d = {} # A regular dictionary
d.setdefault('a', []).append(1)
d.setdefault('a', []).append(2)
d.setdefault('b', []).append(4)
但是很多程序員覺(jué)得 setdefault()
用起來(lái)有點(diǎn)別扭。因?yàn)槊看握{(diào)用都得創(chuàng)建一個(gè)新的初始值的實(shí)例(例子程序中的空列表[])。
一般來(lái)講,創(chuàng)建一個(gè)多值映射字典是很簡(jiǎn)單的。但是,如果你選擇自己實(shí)現(xiàn)的話,那么對(duì)于值的初始化可能會(huì)有點(diǎn)麻煩, 你可能會(huì)像下面這樣來(lái)實(shí)現(xiàn):
d = {}
for key, value in pairs:
if key not in d:
d[key] = []
d[key].append(value)
如果使用 defaultdict
的話代碼就更加簡(jiǎn)潔了:
d = defaultdict(list)
for key, value in pairs:
d[key].append(value)
這一小節(jié)所討論的問(wèn)題跟數(shù)據(jù)處理中的記錄歸類問(wèn)題有大的關(guān)聯(lián)。可以參考1.15小節(jié)的例子。
你想創(chuàng)建一個(gè)字典,并且在迭代或序列化這個(gè)字典的時(shí)候能夠控制元素的順序。
為了能控制一個(gè)字典中元素的順序,你可以使用 collections
模塊中的 OrderedDict
類。 在迭代操作的時(shí)候它會(huì)保持元素被插入時(shí)的順序,示例如下:
from collections import OrderedDict
def ordered_dict():
d = OrderedDict()
d['foo'] = 1
d['bar'] = 2
d['spam'] = 3
d['grok'] = 4
# Outputs "foo 1", "bar 2", "spam 3", "grok 4"
for key in d:
print(key, d[key])
當(dāng)你想要構(gòu)建一個(gè)將來(lái)需要序列化或編碼成其他格式的映射的時(shí)候, OrderedDict
是非常有用的。 比如,你想精確控制以 JSON 編碼后字段的順序,你可以先使用 OrderedDict
來(lái)構(gòu)建這樣的數(shù)據(jù):
>>> import json
>>> json.dumps(d)
'{"foo": 1, "bar": 2, "spam": 3, "grok": 4}'
>>>
OrderedDict
內(nèi)部維護(hù)著一個(gè)根據(jù)鍵插入順序排序的雙向鏈表。每次當(dāng)一個(gè)新的元素插入進(jìn)來(lái)的時(shí)候, 它會(huì)被放到鏈表的尾部。對(duì)于一個(gè)已經(jīng)存在的鍵的重復(fù)賦值不會(huì)改變鍵的順序。
需要注意的是,一個(gè) OrderedDict
的大小是一個(gè)普通字典的兩倍,因?yàn)樗鼉?nèi)部維護(hù)著另外一個(gè)鏈表。 所以如果你要構(gòu)建一個(gè)需要大量 OrderedDict
實(shí)例的數(shù)據(jù)結(jié)構(gòu)的時(shí)候(比如讀取100,000行CSV 數(shù)據(jù)到一個(gè) OrderedDict
列表中去), 那么你就得仔細(xì)權(quán)衡一下是否使用 OrderedDict
帶來(lái)的好處要大過(guò)額外內(nèi)存消耗的影響。
怎樣在數(shù)據(jù)字典中執(zhí)行一些計(jì)算操作(比如求最小值、最大值、排序等等)?
考慮下面的股票名和價(jià)格映射字典:
prices = {
'ACME': 45.23,
'AAPL': 612.78,
'IBM': 205.55,
'HPQ': 37.20,
'FB': 10.75
}
為了對(duì)字典值執(zhí)行計(jì)算操作,通常需要使用 zip()
函數(shù)先將鍵和值反轉(zhuǎn)過(guò)來(lái)。比如,下面是查找最小和最大股票價(jià)格和股票值的代碼:
min_price = min(zip(prices.values(), prices.keys()))
# min_price is (10.75, 'FB')
max_price = max(zip(prices.values(), prices.keys()))
# max_price is (612.78, 'AAPL')
類似的,可以使用 zip()
和 sorted()
函數(shù)來(lái)排列字典數(shù)據(jù):
prices_sorted = sorted(zip(prices.values(), prices.keys()))
# prices_sorted is [(10.75, 'FB'), (37.2, 'HPQ'),
# (45.23, 'ACME'), (205.55, 'IBM'),
# (612.78, 'AAPL')]
執(zhí)行這些計(jì)算的時(shí)候,需要注意的是 zip()
函數(shù)創(chuàng)建的是一個(gè)只能訪問(wèn)一次的迭代器。 比如,下面的代碼就會(huì)產(chǎn)生錯(cuò)誤:
prices_and_names = zip(prices.values(), prices.keys())
print(min(prices_and_names)) # OK
print(max(prices_and_names)) # ValueError: max() arg is an empty sequence
如果你在一個(gè)字典上執(zhí)行普通的數(shù)學(xué)運(yùn)算,你會(huì)發(fā)現(xiàn)它們僅僅作用于鍵,而不是值。比如:
min(prices) # Returns 'AAPL'
max(prices) # Returns 'IBM'
這個(gè)結(jié)果并不是你想要的,因?yàn)槟阆胍谧值涞闹导仙蠄?zhí)行這些計(jì)算?;蛟S你會(huì)嘗試著使用字典的 values()
方法來(lái)解決這個(gè)問(wèn)題:
min(prices.values()) # Returns 10.75
max(prices.values()) # Returns 612.78
不幸的是,通常這個(gè)結(jié)果同樣也不是你想要的。你可能還想要知道對(duì)應(yīng)的鍵的信息(比如那種股票價(jià)格是最低的?)。
你可以在 min()
和 max()
函數(shù)中提供 key
函數(shù)參數(shù)來(lái)獲取最小值或最大值對(duì)應(yīng)的鍵的信息。比如:
min(prices, key=lambda k: prices[k]) # Returns 'FB'
max(prices, key=lambda k: prices[k]) # Returns 'AAPL'
但是,如果還想要得到最小值,你又得執(zhí)行一次查找操作。比如:
min_value = prices[min(prices, key=lambda k: prices[k])]
前面的 zip()
函數(shù)方案通過(guò)將字典”反轉(zhuǎn)”為(值,鍵)元組序列來(lái)解決了上述問(wèn)題。當(dāng)比較兩個(gè)元組的時(shí)候,值會(huì)先進(jìn)行比較,然后才是鍵。這樣的話你就能通過(guò)一條簡(jiǎn)單的語(yǔ)句就能很輕松的實(shí)現(xiàn)在字典上的求最值和排序操作了。
需要注意的是在計(jì)算操作中使用到了(值,鍵)對(duì)。當(dāng)多個(gè)實(shí)體擁有相同的值的時(shí)候,鍵會(huì)決定返回結(jié)果。比如,在執(zhí)行 min()
和 max()
操作的時(shí)候,如果恰巧最小或最大值有重復(fù)的,那么擁有最小或最大鍵的實(shí)體會(huì)返回:
>>> prices = { 'AAA' : 45.23, 'ZZZ': 45.23 }
>>> min(zip(prices.values(), prices.keys()))
(45.23, 'AAA')
>>> max(zip(prices.values(), prices.keys()))
(45.23, 'ZZZ')
>>>
怎樣在兩個(gè)字典中尋尋找相同點(diǎn)(比如相同的鍵、相同的值等等)?
考慮下面兩個(gè)字典:
a = {
'x' : 1,
'y' : 2,
'z' : 3
}
b = {
'w' : 10,
'x' : 11,
'y' : 2
}
為了尋找兩個(gè)字典的相同點(diǎn),可以簡(jiǎn)單的在兩字典的 keys()
或者 items()
方法返回結(jié)果上執(zhí)行集合操作。比如:
# Find keys in common
a.keys() & b.keys() # { 'x', 'y' }
# Find keys in a that are not in b
a.keys() - b.keys() # { 'z' }
# Find (key,value) pairs in common
a.items() & b.items() # { ('y', 2) }
這些操作也可以用于修改或者過(guò)濾字典元素。比如,假如你想以現(xiàn)有字典構(gòu)造一個(gè)排除幾個(gè)指定鍵的新字典。下面利用字典推導(dǎo)來(lái)實(shí)現(xiàn)這樣的需求:
# Make a new dictionary with certain keys removed
c = {key:a[key] for key in a.keys() - {'z', 'w'}}
# c is {'x': 1, 'y': 2}
一個(gè)字典就是一個(gè)鍵集合與值集合的映射關(guān)系。字典的 keys()
方法返回一個(gè)展現(xiàn)鍵集合的鍵視圖對(duì)象。鍵視圖的一個(gè)很少被了解的特性就是它們也支持集合操作,比如集合并、交、差運(yùn)算。 所以,如果你想對(duì)集合的鍵執(zhí)行一些普通的集合操作,可以直接使用鍵視圖對(duì)象而不用先將它們轉(zhuǎn)換成一個(gè) set。
字典的 items()
方法返回一個(gè)包含(鍵,值)對(duì)的元素視圖對(duì)象。這個(gè)對(duì)象同樣也支持集合操作,并且可以被用來(lái)查找兩個(gè)字典有哪些相同的鍵值對(duì)。
盡管字典的 values()
方法也是類似,但是它并不支持這里介紹的集合操作。某種程度上是因?yàn)橹狄晥D不能保證所有的值互不相同,這樣會(huì)導(dǎo)致某些集合操作會(huì)出現(xiàn)問(wèn)題。不過(guò),如果你硬要在值上面執(zhí)行這些集合操作的話,你可以先將值集合轉(zhuǎn)換成 set,然后再執(zhí)行集合運(yùn)算就行了。
怎樣在一個(gè)序列上面保持元素順序的同時(shí)消除重復(fù)的值?
如果序列上的值都是 hashable
類型,那么可以很簡(jiǎn)單的利用集合或者生成器來(lái)解決這個(gè)問(wèn)題。比如:
def dedupe(items):
seen = set()
for item in items:
if item not in seen:
yield item
seen.add(item)
下面是使用上述函數(shù)的例子:
>>> a = [1, 5, 2, 1, 9, 1, 5, 10]
>>> list(dedupe(a))
[1, 5, 2, 9, 10]
>>>
這個(gè)方法僅僅在序列中元素為 hashable
的時(shí)候才管用。如果你想消除元素不可哈希(比如 dict
類型)的序列中重復(fù)元素的話,你需要將上述代碼稍微改變一下,就像這樣:
def dedupe(items, key=None):
seen = set()
for item in items:
val = item if key is None else key(item)
if val not in seen:
yield item
seen.add(val)
這里的 key 參數(shù)指定了一個(gè)函數(shù),將序列元素轉(zhuǎn)換成 hashable 類型。下面是它的用法示例:
>>> a = [ {'x':1, 'y':2}, {'x':1, 'y':3}, {'x':1, 'y':2}, {'x':2, 'y':4}]
>>> list(dedupe(a, key=lambda d: (d['x'],d['y'])))
[{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 2, 'y': 4}]
>>> list(dedupe(a, key=lambda d: d['x']))
[{'x': 1, 'y': 2}, {'x': 2, 'y': 4}]
>>>
如果你想基于單個(gè)字段、屬性或者某個(gè)更大的數(shù)據(jù)結(jié)構(gòu)來(lái)消除重復(fù)元素,第二種方案同樣可以勝任。
如果你僅僅就是想消除重復(fù)元素,通??梢院?jiǎn)單的構(gòu)造一個(gè)集合。比如:
>>> a
[1, 5, 2, 1, 9, 1, 5, 10]
>>> set(a)
{1, 2, 10, 5, 9}
>>>
然而,這種方法不能維護(hù)元素的順序,生成的結(jié)果中的元素位置被打亂。而上面的方法可以避免這種情況。
在本節(jié)中我們使用了生成器函數(shù)讓我們的函數(shù)更加通用,不僅僅是局限于列表處理。比如,如果如果你想讀取一個(gè)文件,消除重復(fù)行,你可以很容易像這樣做:
with open(somefile,'r') as f:
for line in dedupe(f):
...
上述 key 函數(shù)參數(shù)模仿了 sorted()
, min()
和 max()
等內(nèi)置函數(shù)的相似功能。可以參考1.8和1.13小節(jié)了解更多。
你的程序已經(jīng)出現(xiàn)一大堆已無(wú)法直視的硬編碼切片下標(biāo),然后你想清理下代碼。
假定你有一段代碼要從一個(gè)記錄字符串中幾個(gè)固定位置提取出特定的數(shù)據(jù)字段(比如文件或類似格式):
###### 0123456789012345678901234567890123456789012345678901234567890'
record = '....................100 .......513.25 ..........'
cost = int(record[20:23]) * float(record[31:37])
與其那樣寫,為什么不想這樣命名切片呢:
SHARES = slice(20, 23)
PRICE = slice(31, 37)
cost = int(record[SHARES]) * float(record[PRICE])
第二種版本中,你避免了大量無(wú)法理解的硬編碼下標(biāo),使得你的代碼更加清晰可讀了。
一般來(lái)講,代碼中如果出現(xiàn)大量的硬編碼下標(biāo)值會(huì)使得可讀性和可維護(hù)性大大降低。比如,如果你回過(guò)來(lái)看看一年前你寫的代碼,你會(huì)摸著腦袋想那時(shí)候自己到底想干嘛啊。這里的解決方案是一個(gè)很簡(jiǎn)單的方法讓你更加清晰的表達(dá)代碼到底要做什么。
內(nèi)置的 slice()
函數(shù)創(chuàng)建了一個(gè)切片對(duì)象,可以被用在任何切片允許使用的地方。比如:
>>> items = [0, 1, 2, 3, 4, 5, 6]
>>> a = slice(2, 4)
>>> items[2:4]
[2, 3]
>>> items[a]
[2, 3]
>>> items[a] = [10,11]
>>> items
[0, 1, 10, 11, 4, 5, 6]
>>> del items[a]
>>> items
[0, 1, 4, 5, 6]
如果你有一個(gè)切片對(duì)象 s,你可以分別調(diào)用它的 s.start
, s.stop
, s.step
屬性來(lái)獲取更多的信息。比如:
>>> s = slice(5, 50, 2)
>>> s.start
5
>>> s.stop
50
>>> s.step
2
>>>
另外,你還能通過(guò)調(diào)用切片的 indices(size)
方法將它映射到一個(gè)確定大小的序列上,這個(gè)方法返回一個(gè)三元組 (start, stop, step)
,所有值都會(huì)被合適的縮小以滿足邊界限制,從而使用的時(shí)候避免出現(xiàn) IndexError
異常。比如:
>>> s = 'HelloWorld'
>>> a.indices(len(s))
(5, 10, 2)
>>> for i in range(*a.indices(len(s))):
... print(s[i])
...
W
r
d
>>>
怎樣找出一個(gè)序列中出現(xiàn)次數(shù)最多的元素呢?
collections.Counter
類就是專門為這類問(wèn)題而設(shè)計(jì)的,它甚至有一個(gè)有用的 most_common()
方法直接給了你答案。
為了演示,先假設(shè)你有一個(gè)單詞列表并且想找出哪個(gè)單詞出現(xiàn)頻率最高。你可以這樣做:
words = [
'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes',
'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around', 'the',
'eyes', "don't", 'look', 'around', 'the', 'eyes', 'look', 'into',
'my', 'eyes', "you're", 'under'
]
from collections import Counter
word_counts = Counter(words)
# 出現(xiàn)頻率最高的3個(gè)單詞
top_three = word_counts.most_common(3)
print(top_three)
# Outputs [('eyes', 8), ('the', 5), ('look', 4)]
作為輸入, Counter
對(duì)象可以接受任意的 hashable
序列對(duì)象。 在底層實(shí)現(xiàn)上,一個(gè) Counter
對(duì)象就是一個(gè)字典,將元素映射到它出現(xiàn)的次數(shù)上。比如:
>>> word_counts['not']
1
>>> word_counts['eyes']
8
>>>
如果你想手動(dòng)增加計(jì)數(shù),可以簡(jiǎn)單的用加法:
>>> morewords = ['why','are','you','not','looking','in','my','eyes']
>>> for word in morewords:
... word_counts[word] += 1
...
>>> word_counts['eyes']
9
>>>
或者你可以使用 update()
方法:
>>> word_counts.update(morewords)
>>>
Counter
實(shí)例一個(gè)鮮為人知的特性是它們可以很容易的跟數(shù)學(xué)運(yùn)算操作相結(jié)合。比如:
>>> a = Counter(words)
>>> b = Counter(morewords)
>>> a
Counter({'eyes': 8, 'the': 5, 'look': 4, 'into': 3, 'my': 3, 'around': 2,
"you're": 1, "don't": 1, 'under': 1, 'not': 1})
>>> b
Counter({'eyes': 1, 'looking': 1, 'are': 1, 'in': 1, 'not': 1, 'you': 1,
'my': 1, 'why': 1})
>>> # Combine counts
>>> c = a + b
>>> c
Counter({'eyes': 9, 'the': 5, 'look': 4, 'my': 4, 'into': 3, 'not': 2,
'around': 2, "you're": 1, "don't": 1, 'in': 1, 'why': 1,
'looking': 1, 'are': 1, 'under': 1, 'you': 1})
>>> # Subtract counts
>>> d = a - b
>>> d
Counter({'eyes': 7, 'the': 5, 'look': 4, 'into': 3, 'my': 2, 'around': 2,
"you're": 1, "don't": 1, 'under': 1})
>>>
毫無(wú)疑問(wèn), Counter
對(duì)象在幾乎所有需要制表或者計(jì)數(shù)數(shù)據(jù)的場(chǎng)合是非常有用的工具。 在解決這類問(wèn)題的時(shí)候你應(yīng)該優(yōu)先選擇它,而不是手動(dòng)的利用字典去實(shí)現(xiàn)。
你有一個(gè)字典列表,你想根據(jù)某個(gè)或某幾個(gè)字典字段來(lái)排序這個(gè)列表。
通過(guò)使用 operator
模塊的 itemgetter
函數(shù),可以非常容易的排序這樣的數(shù)據(jù)結(jié)構(gòu)。 假設(shè)你從數(shù)據(jù)庫(kù)中檢索出來(lái)網(wǎng)站會(huì)員信息列表,并且以下列的數(shù)據(jù)結(jié)構(gòu)返回:
rows = [
{'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
{'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
]
根據(jù)任意的字典字段來(lái)排序輸入結(jié)果行是很容易實(shí)現(xiàn)的,代碼示例:
from operator import itemgetter
rows_by_fname = sorted(rows, key=itemgetter('fname'))
rows_by_uid = sorted(rows, key=itemgetter('uid'))
print(rows_by_fname)
print(rows_by_uid)
代碼的輸出如下:
[{'fname': 'Big', 'uid': 1004, 'lname': 'Jones'},
{'fname': 'Brian', 'uid': 1003, 'lname': 'Jones'},
{'fname': 'David', 'uid': 1002, 'lname': 'Beazley'},
{'fname': 'John', 'uid': 1001, 'lname': 'Cleese'}]
[{'fname': 'John', 'uid': 1001, 'lname': 'Cleese'},
{'fname': 'David', 'uid': 1002, 'lname': 'Beazley'},
{'fname': 'Brian', 'uid': 1003, 'lname': 'Jones'},
{'fname': 'Big', 'uid': 1004, 'lname': 'Jones'}]
itemgetter()
函數(shù)也支持多個(gè) keys,比如下面的代碼
rows_by_lfname = sorted(rows, key=itemgetter('lname','fname'))
print(rows_by_lfname)
會(huì)產(chǎn)生如下的輸出:
[{'fname': 'David', 'uid': 1002, 'lname': 'Beazley'},
{'fname': 'John', 'uid': 1001, 'lname': 'Cleese'},
{'fname': 'Big', 'uid': 1004, 'lname': 'Jones'},
{'fname': 'Brian', 'uid': 1003, 'lname': 'Jones'}]
在上面例子中, rows
被傳遞給接受一個(gè)關(guān)鍵字參數(shù)的 sorted()
內(nèi)置函數(shù)。 這個(gè)參數(shù)是 callable
類型,并且從 rows
中接受一個(gè)單一元素,然后返回被用來(lái)排序的值。 itemgetter()
函數(shù)就是負(fù)責(zé)創(chuàng)建這個(gè) callable
對(duì)象的。
operator.itemgetter()
函數(shù)有一個(gè)被 rows 中的記錄用來(lái)查找值的索引參數(shù)。可以是一個(gè)字典鍵名稱, 一個(gè)整形值或者任何能夠傳入一個(gè)對(duì)象的 __getitem__()
方法的值。 如果你傳入多個(gè)索引參數(shù)給 itemgetter()
,它生成的 callable
對(duì)象會(huì)返回一個(gè)包含所有元素值的元組, 并且 sorted()
函數(shù)會(huì)根據(jù)這個(gè)元組中元素順序去排序。 但你想要同時(shí)在幾個(gè)字段上面進(jìn)行排序(比如通過(guò)姓和名來(lái)排序,也就是例子中的那樣)的時(shí)候這種方法是很有用的。
itemgetter()
有時(shí)候也可以用 lambda
表達(dá)式代替,比如:
rows_by_fname = sorted(rows, key=lambda r: r['fname'])
rows_by_lfname = sorted(rows, key=lambda r: (r['lname'],r['fname']))
這種方案也不錯(cuò)。但是,使用 itemgetter()
方式會(huì)運(yùn)行的稍微快點(diǎn)。因此,如果你對(duì)性能要求比較高的話就使用 itemgetter()
方式。
最后,不要忘了這節(jié)中展示的技術(shù)也同樣適用于 min()
和 max()
等函數(shù)。比如:
>>> min(rows, key=itemgetter('uid'))
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001}
>>> max(rows, key=itemgetter('uid'))
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
>>>
你想排序類型相同的對(duì)象,但是他們不支持原生的比較操作。
內(nèi)置的 sorted()
函數(shù)有一個(gè)關(guān)鍵字參數(shù) key
,可以傳入一個(gè) callable
對(duì)象給它, 這個(gè) callable
對(duì)象對(duì)每個(gè)傳入的對(duì)象返回一個(gè)值,這個(gè)值會(huì)被 sorted
用來(lái)排序這些對(duì)象。 比如,如果你在應(yīng)用程序里面有一個(gè) User
實(shí)例序列,并且你希望通過(guò)他們的 user_id
屬性進(jìn)行排序, 你可以提供一個(gè)以 User
實(shí)例作為輸入并輸出對(duì)應(yīng) user_id
值的 callable
對(duì)象。比如:
class User:
def __init__(self, user_id):
self.user_id = user_id
def __repr__(self):
return 'User({})'.format(self.user_id)
def sort_notcompare():
users = [User(23), User(3), User(99)]
print(users)
print(sorted(users, key=lambda u: u.user_id))
另外一種方式是使用 operator.attrgetter()
來(lái)代替 lambda 函數(shù):
>>> from operator import attrgetter
>>> sorted(users, key=attrgetter('user_id'))
[User(3), User(23), User(99)]
>>>
選擇使用 lambda 函數(shù)或者是 attrgetter()
可能取決于個(gè)人喜好。但是,attrgetter()
函數(shù)通常會(huì)運(yùn)行的快點(diǎn),并且還能同時(shí)允許多個(gè)字段進(jìn)行比較。 這個(gè)跟 operator.itemgetter()
函數(shù)作用于字典類型很類似(參考1.13小節(jié))。 例如,如果 User
實(shí)例還有一個(gè) first_name
和 last_name
屬性,那么可以向下面這樣排序:
by_name = sorted(users, key=attrgetter('last_name', 'first_name'))
同樣需要注意的是,這一小節(jié)用到的技術(shù)同樣適用于像 min()
和 max()
之類的函數(shù)。比如:
>>> min(users, key=attrgetter('user_id')
User(3)
>>> max(users, key=attrgetter('user_id')
User(99)
>>>
你有一個(gè)字典或者實(shí)例的序列,然后你想根據(jù)某個(gè)特定的字段比如 date
來(lái)分組迭代訪問(wèn)。
itertools.groupby()
函數(shù)對(duì)于這樣的數(shù)據(jù)分組操作非常實(shí)用。為了演示,假設(shè)你已經(jīng)有了下列的字典列表:
rows = [
{'address': '5412 N CLARK', 'date': '07/01/2012'},
{'address': '5148 N CLARK', 'date': '07/04/2012'},
{'address': '5800 E 58TH', 'date': '07/02/2012'},
{'address': '2122 N CLARK', 'date': '07/03/2012'},
{'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'},
{'address': '1060 W ADDISON', 'date': '07/02/2012'},
{'address': '4801 N BROADWAY', 'date': '07/01/2012'},
{'address': '1039 W GRANVILLE', 'date': '07/04/2012'},
]
現(xiàn)在假設(shè)你想在按 date 分組后的數(shù)據(jù)塊上進(jìn)行迭代。為了這樣做,你首先需要按照指定的字段(這里就是 date
)排序, 然后調(diào)用 itertools.groupby()
函數(shù):
from operator import itemgetter
from itertools import groupby
# Sort by the desired field first
rows.sort(key=itemgetter('date'))
# Iterate in groups
for date, items in groupby(rows, key=itemgetter('date')):
print(date)
for i in items:
print(' ', i)
運(yùn)行結(jié)果:
07/01/2012
{'date': '07/01/2012', 'address': '5412 N CLARK'}
{'date': '07/01/2012', 'address': '4801 N BROADWAY'}
07/02/2012
{'date': '07/02/2012', 'address': '5800 E 58TH'}
{'date': '07/02/2012', 'address': '5645 N RAVENSWOOD'}
{'date': '07/02/2012', 'address': '1060 W ADDISON'}
07/03/2012
{'date': '07/03/2012', 'address': '2122 N CLARK'}
07/04/2012
{'date': '07/04/2012', 'address': '5148 N CLARK'}
{'date': '07/04/2012', 'address': '1039 W GRANVILLE'}
groupby()
函數(shù)掃描整個(gè)序列并且查找連續(xù)相同值(或者根據(jù)指定 key 函數(shù)返回值相同)的元素序列。 在每次迭代的時(shí)候,它會(huì)返回一個(gè)值和一個(gè)迭代器對(duì)象, 這個(gè)迭代器對(duì)象可以生成元素值全部等于上面那個(gè)值的組中所有對(duì)象。
一個(gè)非常重要的準(zhǔn)備步驟是要根據(jù)指定的字段將數(shù)據(jù)排序。 因?yàn)?groupby()
僅僅檢查連續(xù)的元素,如果事先并沒(méi)有排序完成的話,分組函數(shù)將得不到想要的結(jié)果。
如果你僅僅只是想根據(jù) date 字段將數(shù)據(jù)分組到一個(gè)大的數(shù)據(jù)結(jié)構(gòu)中去,并且允許隨機(jī)訪問(wèn), 那么你最好使用 defaultdict()
來(lái)構(gòu)建一個(gè)多值字典,關(guān)于多值字典已經(jīng)在1.6小節(jié)有過(guò)詳細(xì)的介紹。比如:
from collections import defaultdict
rows_by_date = defaultdict(list)
for row in rows:
rows_by_date[row['date']].append(row)
這樣的話你可以很輕松的就能對(duì)每個(gè)指定日期訪問(wèn)對(duì)應(yīng)的記錄:
>>> for r in rows_by_date['07/01/2012']:
... print(r)
...
{'date': '07/01/2012', 'address': '5412 N CLARK'}
{'date': '07/01/2012', 'address': '4801 N BROADWAY'}
>>>
在上面這個(gè)例子中,我們沒(méi)有必要先將記錄排序。因此,如果對(duì)內(nèi)存占用不是很關(guān)心, 這種方式會(huì)比先排序然后再通過(guò) groupby()
函數(shù)迭代的方式運(yùn)行得快一些。
你有一個(gè)數(shù)據(jù)序列,想利用一些規(guī)則從中提取出需要的值或者是縮短序列
最簡(jiǎn)單的過(guò)濾序列元素的方法就是使用列表推導(dǎo)。比如:
>>> mylist = [1, 4, -5, 10, -7, 2, 3, -1]
>>> [n for n in mylist if n > 0]
[1, 4, 10, 2, 3]
>>> [n for n in mylist if n < 0]
[-5, -7, -1]
>>>
使用列表推導(dǎo)的一個(gè)潛在缺陷就是如果輸入非常大的時(shí)候會(huì)產(chǎn)生一個(gè)非常大的結(jié)果集,占用大量?jī)?nèi)存。 如果你對(duì)內(nèi)存比較敏感,那么你可以使用生成器表達(dá)式迭代產(chǎn)生過(guò)濾的元素。比如:
>>> pos = (n for n in mylist if n > 0)
>>> pos
<generator object <genexpr> at 0x1006a0eb0>
>>> for x in pos:
... print(x)
...
1
4
10
2
3
>>>
有時(shí)候,過(guò)濾規(guī)則比較復(fù)雜,不能簡(jiǎn)單的在列表推導(dǎo)或者生成器表達(dá)式中表達(dá)出來(lái)。比如,假設(shè)過(guò)濾的時(shí)候需要處理一些異?;蛘咂渌麖?fù)雜情況。這時(shí)候你可以將過(guò)濾代碼放到一個(gè)函數(shù)中,然后使用內(nèi)建的 filter()
函數(shù)。示例如下:
values = ['1', '2', '-3', '-', '4', 'N/A', '5']
def is_int(val):
try:
x = int(val)
return True
except ValueError:
return False
ivals = list(filter(is_int, values))
print(ivals)
# Outputs ['1', '2', '-3', '4', '5']
filter()
函數(shù)創(chuàng)建了一個(gè)迭代器,因此如果你想得到一個(gè)列表的話,就得像示例那樣使用 list()
去轉(zhuǎn)換。
列表推導(dǎo)和生成器表達(dá)式通常情況下是過(guò)濾數(shù)據(jù)最簡(jiǎn)單的方式。其實(shí)它們還能在過(guò)濾的時(shí)候轉(zhuǎn)換數(shù)據(jù)。比如:
>>> mylist = [1, 4, -5, 10, -7, 2, 3, -1]
>>> import math
>>> [math.sqrt(n) for n in mylist if n > 0]
[1.0, 2.0, 3.1622776601683795, 1.4142135623730951, 1.7320508075688772]
>>>
過(guò)濾操作的一個(gè)變種就是將不符合條件的值用新的值代替,而不是丟棄它們。比如,在一列數(shù)據(jù)中你可能不僅想找到正數(shù),而且還想將不是正數(shù)的數(shù)替換成指定的數(shù)。通過(guò)將過(guò)濾條件放到條件表達(dá)式中去,可以很容易的解決這個(gè)問(wèn)題,就像這樣:
>>> clip_neg = [n if n > 0 else 0 for n in mylist]
>>> clip_neg
[1, 4, 0, 10, 0, 2, 3, 0]
>>> clip_pos = [n if n < 0 else 0 for n in mylist]
>>> clip_pos
[0, 0, -5, 0, -7, 0, 0, -1]
>>>
另外一個(gè)值得關(guān)注的過(guò)濾工具就是 itertools.compress()
,它以一個(gè) iterable
對(duì)象和一個(gè)相對(duì)應(yīng)的 Boolean
選擇器序列作為輸入?yún)?shù)。然后輸出 iterable
對(duì)象中對(duì)應(yīng)選擇器為 True
的元素。當(dāng)你需要用另外一個(gè)相關(guān)聯(lián)的序列來(lái)過(guò)濾某個(gè)序列的時(shí)候,這個(gè)函數(shù)是非常有用的。比如,假如現(xiàn)在你有下面兩列數(shù)據(jù):
addresses = [
'5412 N CLARK',
'5148 N CLARK',
'5800 E 58TH',
'2122 N CLARK'
'5645 N RAVENSWOOD',
'1060 W ADDISON',
'4801 N BROADWAY',
'1039 W GRANVILLE',
]
counts = [ 0, 3, 10, 4, 1,