1. 程式人生 > >python3學習筆記4---引用http://python3-cookbook.readthedocs.io/zh_CN/latest/

python3學習筆記4---引用http://python3-cookbook.readthedocs.io/zh_CN/latest/

語言 all 刪除 做到 over nmap 遍歷 values 算數

2018-03-01數據結構與算法(4)

1.16過濾序列元素

最簡單的過濾序列元素的方法就是使用列表推導。比如:

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

  用列表推導的一個潛在缺陷就是如果輸入非常大的時候會產生一個非常大的結果集,占用大量內存。 如果你對內存比較敏感,

那麽你可以使用生成器表達式叠代產生過濾的元素。比如:

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

 有時候,過濾規則比較復雜,不能簡單的在列表推導或者生成器表達式中表達出來。 比如,假設過濾的時候需要處理一些異常或者其他復雜情況。這時候你可以將過濾代碼放到一個函數中, 然後使用內建的 filter() 函數。示例如下:

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‘]

 列表推導和生成器表達式通常情況下是過濾數據最簡單的方式。 其實它們還能在過濾的時候轉換數據。比如:

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

 過濾操作的一個變種就是將不符合條件的值用新的值代替,而不是丟棄它們。 比如,在一列數據中你可能不僅想找到正數,而且還想將不是正數的數替換成指定的數。 通過將過濾條件放到條件表達式中去,可以很容易的解決這個問題,就像這樣:

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

  另外一個值得關註的過濾工具就是 itertools.compress() , 它以一個 iterable 對象和一個相對應的 Boolean 選擇器序列作為輸入參數。 然後輸出 iterable 對象中對應選擇器為 True 的元素。 當你需要用另外一個相關聯的序列來過濾某個序列的時候,這個函數是非常有用的。 比如,假如現在你有下面兩列數據:

 

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, 7, 6, 1]

  現在你想將那些對應 count 值大於5的地址全部輸出,那麽你可以這樣做:

>>> from itertools import compress
>>> more5 = [n > 5 for n in counts]
>>> more5
[False, False, True, False, False, True, True, False]
>>> list(compress(addresses, more5))
[‘5800 E 58TH‘, ‘1060 W ADDISON‘, ‘4801 N BROADWAY‘]
>>>

  

這裏的關鍵點在於先創建一個 Boolean 序列,指示哪些元素符合條件。 然後 compress() 函數根據這個序列去選擇輸出對應位置為 True 的元素。

filter() 函數類似, compress() 也是返回的一個叠代器。因此,如果你需要得到一個列表, 那麽你需要使用 list() 來將結果轉換為列表類型。

1.17從字典中提取子集 

 

prices = {
    ‘ACME‘: 45.23,
    ‘AAPL‘: 612.78,
    ‘IBM‘: 205.55,
    ‘HPQ‘: 37.20,
    ‘FB‘: 10.75
}
# Make a dictionary of all prices over 200
p1 = {key: value for key, value in prices.items() if value > 200}
# Make a dictionary of tech stocks
tech_names = {‘AAPL‘, ‘IBM‘, ‘HPQ‘, ‘MSFT‘}
p2 = {key: value for key, value in prices.items() if key in tech_names}

 大多數情況下字典推導能做到的,通過創建一個元組序列然後把它傳給 dict() 函數也能實現。比如: 

p1 = dict((key, value) for key, value in prices.items() if value > 200)

但是,字典推導方式表意更清晰,並且實際上也會運行的更快些 (在這個例子中,實際測試幾乎比 dcit() 函數方式快整整一倍)。

有時候完成同一件事會有多種方式。比如,第二個例子程序也可以像這樣重寫:

# Make a dictionary of tech stocks
tech_names = { ‘AAPL‘, ‘IBM‘, ‘HPQ‘, ‘MSFT‘ }
p2 = { key:prices[key] for key in prices.keys() & tech_names }

 但是,運行時間測試結果顯示這種方案大概比第一種方案慢 1.6 倍。 如果對程序運行性能要求比較高的話,需要花點時間去做計時測試。 

1.18映射名稱到序列元素

collections.namedtuple() 函數通過使用一個普通的元組對象來幫你解決這個問題。 這個函數實際上是一個返回 Python 中標準元組類型子類的一個工廠方法。 你需要傳遞一個類型名和你需要的字段給它,然後它就會返回一個類,你可以初始化這個類,為你定義的字段傳遞值等。 代碼示例:

>>> from collections import namedtuple
>>> Subscriber = namedtuple(‘Subscriber‘, [‘addr‘, ‘joined‘])
>>> sub = Subscriber(‘[email protected]‘, ‘2012-10-19‘)
>>> sub
Subscriber(addr=‘[email protected]‘, joined=‘2012-10-19‘)
>>> sub.addr
‘[email protected]‘
>>> sub.joined
‘2012-10-19‘
>>>

 盡管 namedtuple 的實例看起來像一個普通的類實例,但是它跟元組類型是可交換的,支持所有的普通元組操作,比如索引和解壓。 比如:

>>> len(sub)
2
>>> addr, joined = sub
>>> addr
‘[email protected]‘
>>> joined
‘2012-10-19‘
>>>

 

命名元組的一個主要用途是將你的代碼從下標操作中解脫出來。 因此,如果你從數據庫調用中返回了一個很大的元組列表,通過下標去操作其中的元素, 當你在表中添加了新的列的時候你的代碼可能就會出錯了。但是如果你使用了命名元組,那麽就不會有這樣的顧慮。

為了說明清楚,下面是使用普通元組的代碼:

def compute_cost(records):
    total = 0.0
    for rec in records:
        total += rec[1] * rec[2]
    return total

下標操作通常會讓代碼表意不清晰,並且非常依賴記錄的結構。 下面是使用命名元組的版本:

from collections import namedtuple

Stock = namedtuple(‘Stock‘, [‘name‘, ‘shares‘, ‘price‘])
def compute_cost(records):
    total = 0.0
    for rec in records:
        s = Stock(*rec)
        total += s.shares * s.price
    return total

 命名元組另一個用途就是作為字典的替代,因為字典存儲需要更多的內存空間。 如果你需要構建一個非常大的包含字典的數據結構,那麽使用命名元組會更加高效。 但是需要註意的是,不像字典那樣,一個命名元組是不可更改的。比如:

>>> s = Stock(‘ACME‘, 100, 123.45)
>>> s
Stock(name=‘ACME‘, shares=100, price=123.45)
>>> s.shares = 75
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can‘t set attribute
>>>

 如果你真的需要改變屬性的值,那麽可以使用命名元組實例的 _replace() 方法, 它會創建一個全新的命名元組並將對應的字段用新的值取代。比如:

>>> s = s._replace(shares=75)
>>> s
Stock(name=‘ACME‘, shares=75, price=123.45)
>>>

 _replace() 方法還有一個很有用的特性就是當你的命名元組擁有可選或者缺失字段時候, 它是一個非常方便的填充數據的方法。 你可以先創建一個包含缺省值的原型元組,然後使用 _replace() 方法創建新的值被更新過的實例。比如: 

 

from collections import namedtuple

Stock = namedtuple(‘Stock‘, [‘name‘, ‘shares‘, ‘price‘, ‘date‘, ‘time‘])

# Create a prototype instance
stock_prototype = Stock(‘‘, 0, 0.0, None, None)

# Function to convert a dictionary to a Stock
def dict_to_stock(s):
    return stock_prototype._replace(**s)

 下面是它的使用方法:

>>> a = {‘name‘: ‘ACME‘, ‘shares‘: 100, ‘price‘: 123.45}
>>> dict_to_stock(a)
Stock(name=‘ACME‘, shares=100, price=123.45, date=None, time=None)
>>> b = {‘name‘: ‘ACME‘, ‘shares‘: 100, ‘price‘: 123.45, ‘date‘: ‘12/17/2012‘}
>>> dict_to_stock(b)
Stock(name=‘ACME‘, shares=100, price=123.45, date=‘12/17/2012‘, time=None)
>>>

 最後要說的是,如果你的目標是定義一個需要更新很多實例屬性的高效數據結構,那麽命名元組並不是你的最佳選擇。 這時候你應該考慮定義一個包含 __slots__ 方法的類 

1.19轉換並同時計算數據

一個非常優雅的方式去結合數據計算與轉換就是使用一個生成器表達式參數。 比如,如果你想計算平方和,可以像下面這樣做:

nums = [1, 2, 3, 4, 5]
s = sum(x * x for x in nums)

 下面是更多的例子: 

 

# Determine if any .py files exist in a directory
import os
files = os.listdir(‘dirname‘)
if any(name.endswith(‘.py‘) for name in files):
    print(‘There be python!‘)
else:
    print(‘Sorry, no python.‘)
# Output a tuple as CSV
s = (‘ACME‘, 50, 123.45)
print(‘,‘.join(str(x) for x in s))
# Data reduction across fields of a data structure
portfolio = [
    {‘name‘:‘GOOG‘, ‘shares‘: 50},
    {‘name‘:‘YHOO‘, ‘shares‘: 75},
    {‘name‘:‘AOL‘, ‘shares‘: 20},
    {‘name‘:‘SCOX‘, ‘shares‘: 65}
]
min_shares = min(s[‘shares‘] for s in portfolio)

  對於小型列表可能沒什麽關系,但是如果元素數量非常大的時候, 它會創建一個巨大的僅僅被使用一次就被丟棄的臨時數據結構。而生成器方案會以叠代的方式轉換數據,因此更省內存。

  在使用一些聚集函數比如 min()max() 的時候你可能更加傾向於使用生成器版本, 它們接受的一個 key 關鍵字參數或許對你很有幫助。 比如,在上面的證券例子中,你可能會考慮下面的實現版本:

# Original: Returns 20
min_shares = min(s[‘shares‘] for s in portfolio)
# Alternative: Returns {‘name‘: ‘AOL‘, ‘shares‘: 20}
min_shares = min(portfolio, key=lambda s: s[‘shares‘])

1.20合並多個字典或映射  

假如你有如下兩個字典:

a = {‘x‘: 1, ‘z‘: 3 }
b = {‘y‘: 2, ‘z‘: 4 }

現在假設你必須在兩個字典中執行查找操作(比如先從 a 中找,如果找不到再在 b 中找)。 一個非常簡單的解決方案就是使用 collections 模塊中的 ChainMap 類。比如:

from collections import ChainMap
c = ChainMap(a,b)
print(c[‘x‘]) # Outputs 1 (from a)
print(c[‘y‘]) # Outputs 2 (from b)
print(c[‘z‘]) # Outputs 3 (from a)

 一個 ChainMap 接受多個字典並將它們在邏輯上變為一個字典。 然後,這些字典並不是真的合並在一起了, ChainMap 類只是在內部創建了一個容納這些字典的列表 並重新定義了一些常見的字典操作來遍歷這個列表。大部分字典操作都是可以正常使用的,比如:

>>> len(c)
3
>>> list(c.keys())
[‘x‘, ‘y‘, ‘z‘]
>>> list(c.values())
[1, 2, 3]
>>>

  

如果出現重復鍵,那麽第一次出現的映射值會被返回。 因此,例子程序中的 c[‘z‘] 總是會返回字典 a 中對應的值,而不是 b 中對應的值。

對於字典的更新或刪除操作總是影響的是列表中第一個字典。比如:

>>> c[‘z‘] = 10
>>> c[‘w‘] = 40
>>> del c[‘x‘]
>>> a
{‘w‘: 40, ‘z‘: 10}
>>> del c[‘y‘]
Traceback (most recent call last):
...
KeyError: "Key not found in the first mapping: ‘y‘"
>>>

 ChainMap 對於編程語言中的作用範圍變量(比如 globals , locals 等)是非常有用的。 事實上,有一些方法可以使它變得簡單: 

>>> values = ChainMap()
>>> values[‘x‘] = 1
>>> # Add a new mapping
>>> values = values.new_child()
>>> values[‘x‘] = 2
>>> # Add a new mapping
>>> values = values.new_child()
>>> values[‘x‘] = 3
>>> values
ChainMap({‘x‘: 3}, {‘x‘: 2}, {‘x‘: 1})
>>> values[‘x‘]
3
>>> # Discard last mapping
>>> values = values.parents
>>> values[‘x‘]
2
>>> # Discard last mapping
>>> values = values.parents
>>> values[‘x‘]
1
>>> values
ChainMap({‘x‘: 1})
>>>

 為 ChainMap 的替代,你可能會考慮使用 update() 方法將兩個字典合並。比如:

>>> a = {‘x‘: 1, ‘z‘: 3 }
>>> b = {‘y‘: 2, ‘z‘: 4 }
>>> merged = dict(b)
>>> merged.update(a)
>>> merged[‘x‘]
1
>>> merged[‘y‘]
2
>>> merged[‘z‘]
3
>>>

 這樣也能行得通,但是它需要你創建一個完全不同的字典對象(或者是破壞現有字典結構)。 同時,如果原字典做了更新,這種改變不會反應到新的合並字典中去。比如: 

 ChainMap 使用原來的字典,它自己不創建新的字典。所以它並不會產生上面所說的結果,比如:

>>> a = {‘x‘: 1, ‘z‘: 3 }
>>> b = {‘y‘: 2, ‘z‘: 4 }
>>> merged = ChainMap(a, b)
>>> merged[‘x‘]
1
>>> a[‘x‘] = 42
>>> merged[‘x‘] # Notice change to merged dicts
42
>>>

  

 

  

 

 

python3學習筆記4---引用http://python3-cookbook.readthedocs.io/zh_CN/latest/