1. 程式人生 > >python 基礎知識點(三)解壓可迭代物件賦值給多個變數

python 基礎知識點(三)解壓可迭代物件賦值給多個變數

問題

如果一個可迭代物件的元素個數超過變數個數時,會丟擲一個 ValueError 。 那麼怎樣才能從這個可迭代物件中解壓出 N 個元素出來?

解決方案

Python 的星號表示式可以用來解決這個問題。比如,你在學習一門課程,在學期末的時候, 你想統計下家庭作業的平均成績,但是排除掉第一個和最後一個分數。如果只有四個分數,你可能就直接去簡單的手動賦值, 但如果有 24 個呢?這時候星號表示式就派上用場了:

def drop_first_last(grades):
    first, *middle, last = grades
    return avg(middle)

另外一種情況,假設你現在有一些使用者的記錄列表,每條記錄包含一個名字、郵件,接著就是不確定數量的電話號碼。 你可以像下面這樣分解這些記錄:

>>> record = ('Dave', '[email protected]', '773-555-1212', '847-555-1212')
>>> name, email, *phone_numbers = record
>>> name
'Dave'
>>> email
'[email protected]'
>>> phone_numbers
[
'773-555-1212', '847-555-1212'] >>>

值得注意的是上面解壓出的 phone_numbers 變數永遠都是列表型別,不管解壓的電話號碼數量是多少(包括 0 個)。 所以,任何使用到 phone_numbers 變數的程式碼就不需要做多餘的型別檢查去確認它是否是列表型別了。

星號表示式也能用在列表的開始部分。比如,你有一個公司前 8 個月銷售資料的序列, 但是你想看下最近一個月資料和前面 7 個月的平均值的對比。你可以這樣做:

*trailing_qtrs, current_qtr = sales_record
trailing_avg 
= sum(trailing_qtrs) / len(trailing_qtrs) return avg_comparison(trailing_avg, current_qtr)

下面是在 Python 直譯器中執行的結果:

>>> *trailing, current = [10, 8, 7, 1, 9, 5, 10, 3]
>>> trailing
[10, 8, 7, 1, 9, 5, 10]
>>> current
3

討論

擴充套件的迭代解壓語法是專門為解壓不確定個數或任意個數元素的可迭代物件而設計的。 通常,這些可迭代物件的元素結構有確定的規則(比如第 1 個元素後面都是電話號碼), 星號表示式讓開發人員可以很容易的利用這些規則來解壓出元素來。 而不是通過一些比較複雜的手段去獲取這些關聯的元素值。

值得注意的是,星號表示式在迭代元素為可變長元組的序列時是很有用的。 比如,下面是一個帶有標籤的元組序列:

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)

星號解壓語法在字串操作的時候也會很有用,比如字串的分割。

程式碼示例:

>>> 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'
>>>

有時候,你想解壓一些元素後丟棄它們,你不能簡單就使用 * , 但是你可以使用一個普通的廢棄名稱,比如 _ 或者 ign (ignore)。

程式碼示例:

>>> record = ('ACME', 50, 123.45, (12, 18, 2012))
>>> name, *_, (*_, year) = record
>>> name
'ACME'
>>> year
2012
>>>

在很多函式式語言中,星號解壓語法跟列表處理有許多相似之處。比如,如果你有一個列表, 你可以很容易的將它分割成前後兩部分:

>>> items = [1, 10, 7, 4, 5, 9]
>>> head, *tail = items
>>> head
1
>>> tail
[10, 7, 4, 5, 9]
>>>

如果你夠聰明的話,還能用這種分割語法去巧妙的實現遞迴演算法。比如:

>>> def sum(items):
...     head, *tail = items
...     return head + sum(tail) if tail else head
...
>>> sum(items)
36
>>>

然後,由於語言層面的限制,遞歸併不是 Python 擅長的。 因此,最後那個遞迴演示僅僅是個好奇的探索罷了,對這個不要太認真了。