一、序列展開與多重賦值
任何數(shù)據(jù)序列(或可迭代對象)都可以只通過一個賦值操作展開自身并同時賦值給多個變量。只需要確保被賦值的變量的數(shù)目和結(jié)構(gòu)與序列相符合即可。如:
>>> 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
序列展開同樣適用于任何可迭代對象(即內(nèi)部實現(xiàn)了 __iter__ 方法的對象,如字符串等),示例如下:
>>> s = 'Hello'
>>> a, b, c, d, e = s
>>> a
'H'
>>> b
'e'
>>> e
'o'
忽略特定的值
在序列展開并賦值時,可以忽略指定項目,方法如下:
>>> data = [ 'ACME', 50, 91.1, (2012, 12, 21) ]
>>> _, shares, price, _ = data
>>> shares
50
>>> price
91.1
展開為固定長度
在序列展開并賦值時,被賦值的變量數(shù)目和結(jié)構(gòu)如果與序列本身不符合,則會報出 ValueError :
>>> p = (4, 5, 6)
>>> x, y = p
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 2)
如上面的示例,當展開的序列長度大于期望的變量數(shù)目時,可以對變量使用 * 操作符將多個項目保存在列表結(jié)構(gòu)中,如:
def drop_first_last(grades):
first, *middle, last = sorted(grades)
return sum(middle) / len(middle)
grades = [ 100, 94, 96, 88, 70, 62, 80 ]
print(f"{ drop_first_last(grades) }")
# => 85.6
print(sum([94, 96, 88, 70, 80]) / 5)
# => 85.6
其他應(yīng)用示例如:
>>> 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']
* 操作符的特性使其可以很方便地應(yīng)用在字符串分割中:
>>> 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'
結(jié)合 _ 還可以寫出如下代碼以在賦值時忽略序列中的多個項目:
>>> record = ('ACME', 50, 123.45, (12, 18, 2012))
>>> name, *_, (*_, year) = record
>>> name
'ACME'
>>> year
2012
二、檢索序列中最大或最小的 N 個項目
Python 的 heapq 模塊包含 nlargest() 和 nsmallest() 函數(shù),可以用來篩選某個數(shù)據(jù)序列中最大或最小的 N 個值。
import heapq
nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
print(heapq.nlargest(3, nums))
print(heapq.nsmallest(3, nums))
# => [42, 37, 23]
# => [-4, 1, 2]
上面兩個函數(shù)也可以接收一個名為 key 的參數(shù),使其可以應(yīng)用在更復(fù)雜的數(shù)據(jù)結(jié)構(gòu)中:
import heapq
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(2, portfolio, key=lambda s: s['price'])
expensive = heapq.nlargest(2, portfolio, key=lambda s: s['price'])
print(cheap)
# => [{'name': 'YHOO', 'shares': 45, 'price': 16.35}, {'name': 'FB', 'shares': 200, 'price': 21.09}]
print(expensive)
# => [{'name': 'AAPL', 'shares': 50, 'price': 543.22}, {'name': 'ACME', 'shares': 75, 'price': 115.65}]
heapq 中的 nlargest() 和 nsmallest() 兩個函數(shù)的原理都是將數(shù)據(jù)序列轉(zhuǎn)換為列表結(jié)構(gòu)并且以堆(heap)的形式進行組織。
heap 最重要的屬性為, heap[0] 永遠是序列中最小的值。使用 heapq.heappop() 方法可以獲取 heap 中的第一個值(即最小值),而之前第二小的值則移動到第一的位置。即不斷調(diào)用 heappop() 可以一直獲取當前序列中最小的值。
>>> 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]
>>> heapq.heappop(nums)
-4
>>> heapq.heappop(nums)
1
>>> heapq.heappop(nums)
2
>>> heapq.heappop(nums)
2
>>> heapq.heappop(nums)
7
PS:如果只想檢索某一個最大值或最小值,min() 或者 max() 更快;
如果 nlargest(N, items) 或 nsmallest(N, items) 中的 N 與 items 集合的大小很接近,則先對集合進行排序再分片的方式更快一點,即 sorted(itmes)[:N]
(實際 nlargest 和 nsmallest 內(nèi)部本身也是這樣實現(xiàn)的)
三、實現(xiàn)一個加權(quán)隊列
以下的代碼實現(xiàn)了一種支持加權(quán)的隊列,即隊列中的項目會按指定的權(quán)重排序,并且每次調(diào)用 pop 方法都可以返回權(quán)重最高的項目。
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]
# test code
q = PriorityQueue()
q.push('foo', 1)
q.push('bar', 5)
q.push('spam', 4)
q.push('grok', 1)
print(q.pop())
# => bar
print(q.pop())
# => spam
print(q.pop())
# => foo
print(q.pop())
# => grok
其中 heapq.heappush() 函數(shù)可以向 _queue 序列中插入數(shù)據(jù),之后再使用 heapq.heappop() 函數(shù)獲取序列中的數(shù)據(jù)時,總能保證取出的數(shù)據(jù)是當時隊列中最小的那個。pop 和 push 操作的復(fù)雜度為 O(logN),比普通列表形式的操作(復(fù)雜度為 O(N))效率更高。
同時數(shù)據(jù)是以元組 (-priority, _index, item) 的形式存入到 _queue 隊列中的,元組可以依據(jù)自身的每個數(shù)據(jù)項依次進行比對并確定大小關(guān)系。因而權(quán)重 -priority 可以作為排序的首要依據(jù)(加負號是為了使權(quán)重高的值更小,可以優(yōu)先被 pop() 返回)。當權(quán)重一樣即 -priority 的值相同時,則根據(jù)插入的順序(_index)返回數(shù)據(jù)。
四、比較字典中的 Value
參考下面的代碼示例:
prices = {
'ACME': 45.23,
'AAPL': 612.78,
'IBM': 205.55,
'HPQ': 37.20,
'FB': 10.75
}
print(min(prices)) # => 'AAPL'
print(max(prices)) # => 'IBM'
從輸出中可以看出,min(prices) 和 max(prices) 函數(shù)只是處理字典 prices 中的 keys,對 values 則不做任何操作。
可以將其改為如下形式:
min(prices.values()) # => 10.75
max(prices.values()) # => 612.78
則此時上述兩個函數(shù)又只對字典中的 values 有效,輸出的結(jié)果中也只包含 values,不包含與之關(guān)聯(lián)的 key 的值。
如果想根據(jù) values 對字典中的數(shù)據(jù)進行排序,同時輸出的結(jié)果中既包含 value,又包含與之關(guān)聯(lián)的 key。則可以使用 zip() 函數(shù)。
zip() 函數(shù)以多個可迭代的對象作為參數(shù),將各對象中位置對應(yīng)的元素打包成一個個元組。
回到前面的需求,則可以將字典的 keys 和 values 拆分到兩個列表中,再通過 zip() 函數(shù)將其中的數(shù)據(jù)合并成一個個元組(等同于之前的鍵值對),而 value 作為元組的第一個元素。
prices = {
'ACME': 45.23,
'AAPL': 612.78,
'IBM': 205.55,
'HPQ': 37.20,
'FB': 10.75
}
for item in zip(prices.values(), prices.keys()):
print(item)
# => (45.23, 'ACME')
# => (612.78, 'AAPL')
# => (205.55, 'IBM')
# => (37.2, 'HPQ')
# => (10.75, 'FB')
max_price = max(zip(prices.values(), prices.keys()))
print(max_price)
# => (612.78, 'AAPL')
五、去除序列中的重復(fù)元素
集合(set)是 Python 中的一種數(shù)據(jù)結(jié)構(gòu),它包含了一系列無序的不重復(fù)元素。因此可以通過將其他類型的數(shù)據(jù)轉(zhuǎn)為 set 類型,為序列中的數(shù)據(jù)去重。
但此種方法不能保留原序列中數(shù)據(jù)項原本的排列順序(因為 set 是無序的)。
>>> a = [1, 5, 2, 1, 9, 1, 5, 10]
>>> set(a)
{1, 2, 5, 9, 10}
下面的函數(shù) dedupe 則實現(xiàn)了去重并保留原本的排列順序:
def dedupe(items):
seen = set()
for item in items:
if item not in seen:
yield item
seen.add(item)
a = [1, 5, 2, 1, 9, 1, 5, 10]
print(list(dedupe(a)))
# => [1, 5, 2, 9, 10]
而對于更復(fù)雜的數(shù)據(jù)結(jié)構(gòu),比如對如下的列表進行去重操作:
[{'x':1, 'y':2}, {'x':1, 'y':3}, {'x':1, 'y':2}, {'x':2, 'y':4}]
則可以將 dedupe 函數(shù)改為如下形式:
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)
a = [{'x':1, 'y':2}, {'x':1, 'y':3}, {'x':1, 'y':2}, {'x':2, 'y':4}]
print(list(dedupe(a, key=lambda d: (d['x'],d['y']))))
# => [{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 2, 'y': 4}]
print(list(dedupe(a, key=lambda d: (d['x']))))
# => [{'x': 1, 'y': 2}, {'x': 2, 'y': 4}]
額,自行體會吧。。。
六、找出序列中最常出現(xiàn)的項
collections.Counter 類可以用來尋找序列中出現(xiàn)次數(shù)最多的幾個項目。
from collections import Counter
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'
]
word_counts = Counter(words)
top_three = word_counts.most_common(3)
print(top_three)
# => [('eyes', 8), ('the', 5), ('look', 4)]
morewords = ['why', 'are', 'you', 'not', 'looking', 'in', 'my', 'eyes']
word_counts.update(morewords)
print(word_counts.most_common(3))
# => [('eyes', 9), ('the', 5), ('look', 4)]
a = Counter(words)
b = Counter(morewords)
print(a + b)
# => Counter({'eyes': 9, 'the': 5, 'look': 4, 'my': 4, 'into': 3, 'not': 2, 'around': 2, "don't": 1, "you're": 1, 'under': 1, 'why': 1, 'are': 1, 'you': 1, 'looking': 1, 'in': 1})