鍍金池/ 教程/ Python/ 內(nèi)置類型
附錄
進程通信
操作系統(tǒng)
迭代器
模塊
描述符
裝飾器
第三部分 擴展庫
內(nèi)置類型
數(shù)據(jù)存儲
數(shù)據(jù)類型
基本環(huán)境
文件與目錄
異常
程序框架
數(shù)學運算
函數(shù)
元類
字符串
表達式

內(nèi)置類型

按照用途不同,Python 內(nèi)置類型可分為 "數(shù)據(jù)" 和 "程序" 兩大類。

數(shù)據(jù)類型:

  • 空值: None
  • 數(shù)字: bool, int, long, float, complex
  • 序列: str, unicode, list, tuple
  • 字典: dict
  • 集合: set, frozenset

數(shù)字

bool

None、0、空字符串、以及沒有元素的容器對象都可視為 False,反之為 True。

>>> map(bool, [None, 0, "", u"", list(), tuple(), dict(), set(), frozenset()])
[False, False, False, False, False, False, False, False, False]

雖然有點古怪,但 True、False 的確可以當數(shù)字使用。

>>> int(True)
1
>>> int(False)
0
>>> range(10)[True]
1

>>> x = 5
>>> range(10)[x > 3]
1

int

在 64 位平臺上,int 類型是 64 位整數(shù) (sys.maxint),這顯然能應對絕大多數(shù)情況。整數(shù)是虛擬機特殊照顧對象:

  • 從堆上按需申請名為 PyIntBlock 的緩存區(qū)域存儲整數(shù)對象。
  • 使用固定數(shù)組緩存 [-5, 257) 之間的小數(shù)字,只需計算下標就能獲得指針。
  • PyIntBlock 內(nèi)存不會返還給操作系統(tǒng),直至進程結(jié)束。

看看 "小數(shù)字" 和 "大數(shù)字" 的區(qū)別:

>>> a = 15
>>> b = 15

>>> a is b
True

>>> sys.getrefcount(a)
47

>>> a = 257
>>> b = 257

>>> a is b
False

>>> sys.getrefcount(a)
2

因 PyIntBlock 內(nèi)存只復用不回收,同時持有大量整數(shù)對象將導致內(nèi)存暴漲,且不會在這些對象被回收后釋放內(nèi)存,造成事實上的內(nèi)存泄露。

用 range 創(chuàng)建一個巨大的數(shù)字列表,這就需要足夠多的 PyIntBlock 為數(shù)字對象提供存儲空間。但換成 xrange 就不同了,每次迭代后,數(shù)字對象被回收,其占用內(nèi)存空閑出來并被復用,內(nèi)存也就不會暴漲了。

運行下面測試代碼前,必須先安裝 psutil 包,用來獲取內(nèi)存統(tǒng)計數(shù)據(jù)。

$ sudo easy_install -U psutil
$ cat test.py
#/usr/bin/env python

import gc, os, psutil

def test():
    x = 0
    for i in range(10000000): # xrange
        x += i

    return x

def main():
    print test()
    gc.collect()

    p = psutil.Process(os.getpid())
    print p.get_memory_info()

if __name__ == "__main__":
    main()

對比 range 和 xrange 所需的 RSS 值。

range: meminfo(rss=93339648L, vms=2583552000L) # 89 MB
xrange: meminfo(rss=8638464L, vms=2499342336L) # 8 MB

在實際開發(fā)中,很少會遇到這樣的情形。就算是海量整數(shù)去重、排序,我們也可用位圖等算法來節(jié)約內(nèi)存使用。Python 3 已經(jīng)用 xrange 替換掉了默認的 range,我們使用 2.x 時稍微注意一下即可。

long

當超出 int 限制時, 會自動轉(zhuǎn)換成 long。 作為變長對象,只要有內(nèi)存足夠,足以存儲無法想象的天文數(shù)字。

>>> a = sys.maxint
>>> type(a)
<type 'int'>

>>> b = a + 1   # 超出,自動使用 long 類型。
>>> type(b)
<type 'long'>

>>> 1 << 3000
12302319221611....890612250135171889174899079911291512399773872178519018229989376L

>>> sys.getsizeof(1 << 0xFFFFFFFF)
572662332

使用 long 的機會不多,Python 也就沒有必要專門為其設計優(yōu)化策略。

float

使用雙精度浮點數(shù) (float),不能 "精確" 表示某些十進制的小數(shù)值。尤其是 "四舍五入 (round)" 的結(jié)果,可能和預想不同。

>>> 3 / 2   # 除法默認返回整數(shù),在 Python 3 中返回浮點數(shù)。
1

>>> float(3) / 2
1.5

>>> 3 * 0.1 == 0.3  # 這個容易導致莫名其妙的錯誤。
False

>>> round(2.675, 2) # 并沒有想象中的四舍五入。
2.67

如果需要,可用 Decimal 代替,它能精確控制運算精度、有效數(shù)位和 round 的結(jié)果。

>>> from decimal import Decimal, ROUND_UP, ROUND_DOWN

>>> float('0.1') * 3 == float('0.3')     # float 轉(zhuǎn)型精度不同
False

>>> Decimal('0.1') * 3 == Decimal('0.3')    # decimal 沒有問題
True

>>> Decimal('2.675').quantize(Decimal('.01'), ROUND_UP)  # 精確控制 round
Decimal('2.68')

>>> Decimal('2.675').quantize(Decimal('.01'), ROUND_DOWN)
Decimal('2.67')

在內(nèi)存管理上,float 也采用 PyFloatBlock 模式,但沒有特殊的 "小浮點數(shù)"。

字符串

與字符串相關的問題總是很多,比如池化 (intern)、編碼 (encode) 等。字符串是不可變類型,保存字符序列或二進制數(shù)據(jù)。

  • 短字符串存儲在 arena 區(qū)域, str、unicode 單字符會被永久緩存。
  • str 沒有緩存機制,unicode 則保留 1024 個寬字符長度小于 9 的復用對象。
  • 內(nèi)部包含 hash 值,str 另有標記用來判斷是否被池化。

字符串常量定義簡單自由,可以是單引號、雙引號或三引號。但我個人建議用雙引號表示字符串,用單引號表示字符,和其他語言習慣保持一致。

>>> "It's a book."    # 雙引號里面可以用單引號。
"It's a book."

>>> 'It\'s a book.'   # 轉(zhuǎn)義
"It's a book."

>>> '{"name":"Tom"}'   # 單引號里面正常使用雙引號。
'{"name":"Tom"}'

>>> """     # 多行
... line 1
... line 2
... """

>>> r"abc\x"    # r 前綴定義非轉(zhuǎn)義的 raw-string。
'abc\\x'

>>> "a" "b" "c"    # 自動合并多個相鄰字符串。
'abc'

>>> "中國人"     # UTF-8 字符串 (Linux 系統(tǒng)默認)。
'\xe4\xb8\xad\xe5\x9b\xbd\xe4\xba\xba'

>>> type(s), len(s)
<type 'str'>, 9

>>> u"中國人"    # 使用 u 前綴定義 UNICODE 字符串。
u'\u4e2d\u56fd\u4eba'

>>> type(u), len(u)
<type 'unicode'>, 3

基本操作:

>>> "a" + "b"
'ab'

>>> "a" * 3
'aaa'

>>> ",".join(["a", "b", "c"])     # 合并多個字符串。
'a,b,c'

>>> "a,b,c".split(",")      # 按指定字符分割。
['a', 'b', 'c']

>>> "a\nb\r\nc".splitlines()     # 按行分割。
['a', 'b', 'c']

>>> "a\nb\r\nc".splitlines(True)    # 分割后,保留換行符。
['a\n', 'b\r\n', 'c']

>>> "abc".startswith("ab"), "abc".endswith("bc")  # 判斷是否以特定子串開始或結(jié)束。
True, True

>>> "abc".upper(), "Abc".lower()    # 大小寫轉(zhuǎn)換。
'ABC', 'abc'

>>> "abcabc".find("bc"), "abcabc".find("bc", 2)  # 可指定查找起始結(jié)束位置。
1, 4

>>> " abc".lstrip(), "abc ".rstrip(), " abc ".strip() # 剔除前后空格。
'abc', 'abc', 'abc'

>>> "abc".strip("ac")      # 可刪除指定的前后綴字符。
'b'

>>> "abcabc".replace("bc", "BC")    # 可指定替換次數(shù)。
'aBCaBC'

>>> "a\tbc".expandtabs(4)     # 將 tab 替換成空格。
'a bc'

>>> "123".ljust(5, '0'), "456".rjust(5, '0'), "abc".center(10, '*') # 填充
'12300', '00456', '***abc****'

>>> "123".zfill(6), "123456".zfill(4)      # 數(shù)字填充
'000123', '123456'

編碼

Python 2.x 默認采用 ASCII 編碼。為了完成編碼轉(zhuǎn)換,必須和操作系統(tǒng)字符編碼統(tǒng)一起來。

>>> import sys, locale

>>> sys.getdefaultencoding()  # Python 默認編碼。
'ascii'

>>> c = locale.getdefaultlocale(); c # 獲取當前系統(tǒng)編碼。
('zh_CN', 'UTF-8')

>>> reload(sys)    # setdefaultencoding 在被初始化時被 site.py 刪掉了。
<module 'sys' (built-in)>

>>> sys.setdefaultencoding(c[1]) # 重新設置默認編碼。

str、unicode 都提供了 encode 和 decode 編碼轉(zhuǎn)換方法。

  • encode: 將默認編碼轉(zhuǎn)換為其他編碼。
  • decode: 將默認或者指定編碼字符串轉(zhuǎn)換為 unicode。
>>> s = "中國人"; s
'\xe4\xb8\xad\xe5\x9b\xbd\xe4\xba\xba'

>>> u = s.decode(); u   # UTF-8 -> UNICODE
u'\u4e2d\u56fd\u4eba'

>>> gb = s.encode("gb2312"); gb  # UTF-8 -> GB2312
'\xd6\xd0\xb9\xfa\xc8\xcb'

>>> gb.encode("utf-8")   # encode 會把 gb 當做默認 UTF-8 編碼,所以出錯。
UnicodeDecodeError: 'utf8' codec can't decode byte 0xd6 in position 0: invalid
continuation byte

>>> gb.decode("gb2312")   # 可以將其轉(zhuǎn)換成 UNICODE。
u'\u4e2d\u56fd\u4eba'

>>> gb.decode("gb2312").encode() # 然后再轉(zhuǎn)換成 UTF-8
'\xe4\xb8\xad\xe5\x9b\xbd\xe4\xba\xba'

>>> unicode(gb, "gb2312")  # GB2312 -> UNICODE
u'\u4e2d\u56fd\u4eba'
>>> u.encode()    # UNICODE -> UTF-8
'\xe4\xb8\xad\xe5\x9b\xbd\xe4\xba\xba'

>>> u.encode("gb2312")   # UNICODE -> GB2312
'\xd6\xd0\xb9\xfa\xc8\xcb'

標準庫另有 codecs 模塊用來處理更復雜的編碼轉(zhuǎn)換,比如大小端和 BOM。

>>> from codecs import BOM_UTF32_LE

>>> s = "中國人"
>>> s
'\xe4\xb8\xad\xe5\x9b\xbd\xe4\xba\xba'

>>> s.encode("utf-32")
'\xff\xfe\x00\x00-N\x00\x00\xfdV\x00\x00\xbaN\x00\x00'

>>> BOM_UTF32_LE
'\xff\xfe\x00\x00'

>>> s.encode("utf-32").decode("utf-32")
u'\u4e2d\u56fd\u4eba'

格式化

Python 提供了兩種字符串格式化方法,除了熟悉的 C 樣式外,還有更強大的 format。

%[(key)][flags][width][.precision]typecode

標記:- 左對齊,+ 數(shù)字符號,# 進制前綴,或者用空格、0 填充。

>>> "%(key)s=%(value)d" % dict(key = "a", value = 10)  # key
'a=10'

>>> "[%-10s]" % "a"      # 左對齊
'[a ]'

>>> "%+d, %+d" % (-10, 10)     # 數(shù)字符號
'-10, +10'

>>> "%010d" % 3       # 填充
'0000000003'

>>> "%.2f" % 0.1234      # 小數(shù)位
'0.12'

>>> "%#x, %#X" % (100, 200)     # 十六進制、前綴、大小寫。
'0x64, 0XC8'

>>> "%s, %r" % (m, m)      # s: str(); r: repr()
'test..., <__main__.M object at 0x103c4aa10>'

format 方法支持更多的數(shù)據(jù)類型,包括列表、字典、對象成員等。

{fieldconvertflag:formatspec}

格式化規(guī)范:

formatspec: [[fill]align][sign][#][0][width][.precision][typecode]

示例:

>>> "{key}={value}".format(key="a", value=10) # 使用命名參數(shù)。
'a=10'

>>> "{0},{1},{0}".format(1, 2)    # field 可多次使用。
'1,2,1'

>>> "{0:,}".format(1234567)    # 千分位符號
'1,234,567'

>>> "{0:,.2f}".format(12345.6789)   # 千分位,帶小數(shù)位。
'12,345.68'

>>> "[{0:<10}], [{0:^10}], [{0:*>10}]".format("a") # 左中右對齊,可指定填充字符。
'[a ], [ a ], [*********a]'

>>> import sys

>>> "{0.platform}".format(sys)    # 成員
'darwin'

>>> "{0[a]}".format(dict(a=10, b=20))   # 字典
'10'

>>> "{0[5]}".format(range(10))    # 列表
'5'

另有 string.Template 模板可供使用。該模塊還定義了各種常見的字符序列。

>>> from string import letters, digits, Template

>>> letters         # 字母表
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'

>>> digits         # 數(shù)字表
'0123456789'

>>> Template("$name, $age").substitute(name = "User1", age = 20) # 模板替換。
'User1, 20'

>>> Template("${name}, $age").safe_substitute(name = "User1")  # 沒找到值,不會拋出異常。
'User1, $age'

池化

在 Python 進程中,無數(shù)的對象擁有一堆類似 "name"、"doc" 這樣的名字,池化有助于減少對象數(shù)量和內(nèi)存消耗, 提升性能。

用 intern() 函數(shù)可以把運行期動態(tài)生成的字符串池化。

>>> s = "".join(["a", "b", "c"])

>>> s is "abc"   # 顯然動態(tài)生成的字符串 s 沒有被池化。
False

>>> intern(s) is "abc"  # intern 會檢查內(nèi)部標記。
True

>>> intern(s) is intern(s) # 以后用 intern 從池中獲取字符串對象,就可以復用了。
True

當池化的字符串不再有引用時,將被回收。

列表

從功能上看,列表 (list) 類似 Vector,而非數(shù)組或鏈表。

  • 列表對象和存儲元素指針的數(shù)組是分開的兩塊內(nèi)存,后者在堆上分配。
  • 虛擬機會保留 80 個列表復用對象,但其元素指針數(shù)組會被釋放。
  • 列表會動態(tài)調(diào)整指針數(shù)組大小,預分配內(nèi)存多于實際元素數(shù)量。

創(chuàng)建列表:

>>> []       # 空列表。
[]

>>> ['a', 'b'] * 3      # 這個少見吧。
['a', 'b', 'a', 'b', 'a', 'b']

>>> ['a', 'b'] + ['c', 'd']    # 連接多個列表。
['a', 'b', 'c', 'd']

>>> list("abcd")      # 將序列類型或迭代器轉(zhuǎn)換為列表。
['a', 'b', 'c', 'd']

>>> [x for x in range(3)]    # 生成器表達式。
[0, 1, 2]

常見操作:

>>> l = list("abc")
>>> l[1] = 2      # 按序號讀寫。
>>> l       
['a', 2, 'c']

>>> l = list(xrange(10))
>>> l[2:-2]       # 切片。
[2, 3, 4, 5, 6, 7]

>>> l = list("abcabc")
>>> l.count("b")      # 統(tǒng)計元素項。
2

>>> l = list("abcabc")
>>> l.index("a", 2)     # 從指定位置查找項,返回序號。
3

>>> l = list("abc")
>>> l.append("d")
>>> l        # 追加元素。
['a', 'b', 'c', 'd']

>>> l = list("abc")
>>> l.insert(1, 100)     # 在指定位置插入元素。
>>> l 
['a', 100, 'b', 'c']

>>> l = list("abc")
>>> l.extend(range(3))     # 合并列表。
>>> l 
['a', 'b', 'c', 0, 1, 2]

>>> l = list("abcabc")
>>> l.remove("b")      # 移除第一個指定元素。
>>> l 
['a', 'c', 'a', 'b', 'c']

>>> l = list("abc")
>>> l.pop(1)      # 彈出指定位置的元素 (默認最后項)。
'b'
>>> l   
['a', 'c']

可用 bisect 向有序列表中插入元素。

>>> import bisect

>>> l = ["a", "d", "c", "e"]
>>> l.sort()
>>> l
['a', 'c', 'd', 'e']

>>> bisect.insort(l, "b")
>>> l
['a', 'b', 'c', 'd', 'e']

>>> bisect.insort(l, "d")
>>> l
['a', 'b', 'c', 'd', 'd', 'e']

性能

列表用 realloc() 調(diào)整指針數(shù)組內(nèi)存大小,可能需要復制數(shù)據(jù)。插入和刪除操作,還會循環(huán)移動后續(xù)元素。這些都是潛在的性能隱患。對于頻繁增刪元素的大型列表,應該考慮用鏈表等數(shù)據(jù)結(jié)構代替。

下面的例子測試了兩種創(chuàng)建列表對象方式的性能差異。為獲得更好測試結(jié)果,我們關掉 GC,元素使用同一個小整數(shù)對象,減少其他干擾因素。

>>> import itertools, gc

>>> gc.disable()

>>> def test(n):
...     return len([0 for i in xrange(n)])  # 先創(chuàng)建列表,然后 append。

>>> def test2(n):
...     return len(list(itertools.repeat(0, n))) # 按照迭代器創(chuàng)建列表對象,一次分配內(nèi)存。

>>> timeit test(10000)
1000 loops, best of 3: 810 us per loop

>>> timeit test2(10000)
10000 loops, best of 3: 89.5 us per loop

從測試結(jié)果來看,性能差異非常大。

某些時候,可以考慮用數(shù)組代替列表。 和列表存儲對象指針不同,數(shù)組直接內(nèi)嵌數(shù)據(jù),既省了創(chuàng)建對象的內(nèi)存開銷,又提升了讀寫效率。

>>> import array

>>> a = array.array("l", range(10))  # 用其他序列類型初始化數(shù)組。
>>> a
array('l', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

>>> a.tolist()     # 轉(zhuǎn)換為列表。
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> a = array.array("c")    # 創(chuàng)建特定類型數(shù)組。

>>> a.fromstring("abc")    # 從字符串添加元素。
>>> a
array('c', 'abc')

>>> a.fromlist(list("def"))   # 從列表添加元素。
>>> a
array('c', 'abcdef')

>>> a.extend(array.array("c", "xyz"))  # 合并列表或數(shù)組。
>>> a
array('c', 'abcdefxyz')

元組

元組 (tuple) 看上去像列表的只讀版本,但在底層實現(xiàn)上有很多不同之處。

  • 只讀對象,元組和元素指針數(shù)組內(nèi)存是一次性連續(xù)分配的。
  • 虛擬機緩存 n 個元素數(shù)量小于 20 的元組復用對象。

在編碼中,應該盡可能用元組代替列表。除內(nèi)存復用更高效外,其只讀特征更利于并行開發(fā)。

基本操作:

>>> a = (4)     # 少了逗號,就成了普通的括號運算符了。
>>> type(a)
<type 'int'>

>>> a = (4,)    # 這才是元組。
>>> type(a)
<type 'tuple'>

>>> s = tuple("abcadef")   # 將其他序列類型轉(zhuǎn)換成元組。
>>> s
('a', 'b', 'c', 'a', 'd', 'e', 'f')

>>> s.count("a")    # 元素統(tǒng)計。
2

>>> s.index("d")    # 查找元素,返回序號。
4

標準庫另提供了特別的 namedtuple,可用名字訪問元素項。

>>> from collections import namedtuple

>>> User = namedtuple("User", "name age") # 空格分隔字段名,或使用迭代器。

>>> u = User("user1", 10)
>>> u.name, u.age
('user1', 10)

其實 namedtuple 并不是元組,而是利用模板動態(tài)創(chuàng)建的自定義類型。

字典

字典 (dict) 采用開放地址法的哈希表實現(xiàn)。

  • 自帶元素容量為 8 的 smalltable,只有 "超出" 時才到堆上額外分配元素表內(nèi)存。
  • 虛擬機緩存 80 個字典復用對象,但在堆上分配的元素表內(nèi)存會被釋放。
  • 按需動態(tài)調(diào)整容量。擴容或收縮操作都將重新分配內(nèi)存,重新哈希。
  • 刪除元素操作不會立即收縮內(nèi)存。

創(chuàng)建字典:

>>> {}      # 空字典
{}

>>> {"a":1, "b":2}     # 普通構造方式
{'a': 1, 'b': 2}

>>> dict(a = 1, b = 2)    # 構造
{'a': 1, 'b': 2}

>>> dict((["a", 1], ["b", 2]))   # 用兩個序列類型構造字典。
{'a': 1, 'b': 2}

>>> dict(zip("ab", range(2)))   # 同上
{'a': 0, 'b': 1}

>>> dict(map(None, "abc", range(2)))  # 同上
{'a': 0, 'c': None, 'b': 1}

>>> dict.fromkeys("abc", 1)   # 用序列做 key,并提供默認 value。
{'a': 1, 'c': 1, 'b': 1}

>>> {k:v for k, v in zip("abc", range(3))} # 使用生成表達式構造字典。
{'a': 0, 'c': 2, 'b': 1}

基本操作:

>>> d = {"a":1, "b":2}
>>> "b" in d     # 判斷是否包含 key。
True

>>> d = {"a":1, "b":2}
>>> del d["b"]     # 刪除 k/v。
>>> d
{'a': 1}

>>> d = {"a":1}
>>> d.update({"c": 3})    # 合并 dict。
>>> d
{'a': 1, 'c': 3}

>>> d = {"a":1, "b":2}
>>> d.pop("b")     # 彈出 value。
>>> d
(2, {'a': 1})

>>> d = {"a":1, "b":2}
>>> d.popitem()     # 彈出 (key, value)。
('a', 1)

默認返回值:

>>> d = {"a":1, "b":2}

>>> d.get("c")     # 如果沒有對應 key,返回 None。
None

>>> d.get("d", 123)    # 如果沒有對應 key,返回缺省值。
123

>>> d.setdefault("a", 100)   # key 存在,直接返回 value。
1

>>> d.setdefault("c", 200)    # key 不存在,先設置,后返回。
200

>>> d
{'a': 1, 'c': 200, 'b': 2}

迭代器操作:

>>> d = {"a":1, "b":2}

>>> d.keys()
['a', 'b']

>>> d.values()
[1, 2]

>>> d.items()
[('a', 1), ('b', 2)]

>>> for k in d: print k, d[k]
a 1
b 2

>>> for k, v in d.items(): print k, v
a 1
b 2

對于大字典,調(diào)用 keys()、values()、items() 會構造同樣巨大的列表。建議用迭代器替代,以減少內(nèi)存開銷。

>>> d = {"a":1, "b":2}

>>> d.iterkeys()
<dictionary-keyiterator object at 0x10de82cb0>

>>> d.itervalues()
<dictionary-valueiterator object at 0x10de82d08>

>>> d.iteritems()
<dictionary-itemiterator object at 0x10de82d60>

>>> for k, v in d.iteritems():
... print k, v
a 1
b 2

視圖

要判斷兩個字典間的差異,使用視圖是最簡便的做法。

>>> d1 = dict(a = 1, b = 2)
>>> d2 = dict(b = 2, c = 3)

>>> d1 & d2     # 字典不支持該操作。
TypeError: unsupported operand type(s) for &: 'dict' and 'dict'

>>> v1 = d1.viewitems()
>>> v2 = d2.viewitems()

>>> v1 & v2     # 交集
set([('b', 2)])

>>> v1 | v2     # 并集
set([('a', 1), ('b', 2), ('c', 3)])

>>> v1 - v2     # 差集 (僅 v1 有,v2 沒有的)
set([('a', 1)])

>>> v1 ^ v2     # 對稱差集 (不會同時出現(xiàn)在 v1 和 v2 中)
set([('a', 1), ('c', 3)])

>>> ('a', 1) in v1    # 判斷
True

視圖讓某些操作變得更加簡便,比如在不引入新數(shù)據(jù)項的情況下更新字典內(nèi)容。

>>> a = dict(x=1)
>>> b = dict(x=10, y=20)
>>> a.update({k:b[k] for k in a.viewkeys() & b.viewkeys()})
>>> a
{'x': 10}

視圖會和字典同步變更。

>>> d = {"a": 1}
>>> v = d.viewitems()
>>> v
dict_items([('a', 1)])

>>> d["b"] = 2
>>> v
dict_items([('a', 1), ('b', 2)])

>>> del d["a"]
>>> v
dict_items([('b', 2)])

擴展

當訪問的 key 不存在時, defaultdict 自動調(diào)用 factory 對象創(chuàng)建所需鍵值對。factory 可以是任何無參數(shù)函數(shù)或 callable 對象。

>>> from collections import defaultdict

>>> d = defaultdict(list)

>>> d["a"].append(1) # key "a" 不存在,直接用 list() 函數(shù)創(chuàng)建一個空列表作為 value。
>>> d["a"].append(2)
>>> d["a"]
[1, 2]

字典是哈希表,默認迭代是無序的。如果希望按照元素添加順序輸出結(jié)果,可以用 OrderedDict。

>>> from collections import OrderedDict

>>> d = dict()
>>> d["a"] = 1
>>> d["b"] = 2
>>> d["c"] = 3

>>> for k, v in d.items(): print k, v  # 并非按添加順序輸出。
a 1
c 3
b 2

>>> od = OrderedDict()
>>> od["a"] = 1
>>> od["b"] = 2
>>> od["c"] = 3

>>> for k, v in od.items(): print k, v  # 按添加順序輸出。
a 1
b 2
c 3

>>> od.popitem()     # 按 LIFO 順序彈出。
('c', 3)
>>> od.popitem()
('b', 2)
>>> od.popitem()
('a', 1)

集合

集合 (set) 用來存儲無序不重復對象。所謂不重復對象,除了不是同一對象外,還包括 "值" 不能相同。集合只能存儲可哈希對象,一樣有只讀版本 frozenset。

判重公式:(a is b) or (hash(a) == hash(b) and eq(a, b))

在內(nèi)部實現(xiàn)上,集合和字典非常相似,除了 Entry 沒有 value 字段。集合不是序列類型,不能像列表那樣按序號訪問,也不能做切片操作。

>>> s = set("abc")     # 通過序列類型初始化。
>>> s
set(['a', 'c', 'b'])

>>> {v for v in "abc"}    # 通過構造表達式創(chuàng)建。
set(['a', 'c', 'b'])

>>> "b" in s     # 判斷元素是否在集合中。
True

>>> s.add("d")     # 添加元素
>>> s
set(['a', 'c', 'b', 'd'])

>>> s.remove("b")     # 移除元素
>>> s
set(['a', 'c', 'd'])

>>> s.discard("a")     # 如果存在,就移除。
>>> s
set(['c', 'd'])

>>> s.update(set("abcd"))   # 合并集合
>>> s
set(['a', 'c', 'b', 'd'])

>>> s.pop()      # 彈出元素
'a'
>>> s
set(['c', 'b', 'd'])

集合和字典、列表最大的不同除了元素不重復外,還支持集合運算。

>>> "c" in set("abcd")    # 判斷集合中是否有特定元素。
True

>>> set("abc") is set("abc")
False

>>> set("abc") == set("abc")   # 相等判斷
True

>>> set("abc") = set("abc")   # 不等判斷
False

>>> set("abcd") >= set("ab")   # 超集判斷 (issuperset)
True

>>> set("bc") < set("abcd")   # 子集判斷 (issubset)
True

>>> set("abcd") | set("cdef")   # 并集 (union)
set(['a', 'c', 'b', 'e', 'd', 'f'])

>>> set("abcd") & set("abx")   # 交集 (intersection)
set(['a', 'b'])

>>> set("abcd") - set("ab")   # 差集 (difference), 僅左邊有,右邊沒有的。
set(['c', 'd'])    

>>> set("abx") ^ set("aby")   # 對稱差集 (symmetric_difference)
set(['y', 'x'])     # 不會同時出現(xiàn)在兩個集合當中的元素。

>>> set("abcd").isdisjoint("ab")  # 判斷是否沒有交集
False

更新操作:

>>> s = set("abcd")
>>> s |= set("cdef")    # 并集 (update)
>>> s
set(['a', 'c', 'b', 'e', 'd', 'f'])

>>> s = set("abcd")
>>> s &= set("cdef")    # 交集 (intersection_update)
>>> s
set(['c', 'd'])

>>> s = set("abx")
>>> s -= set("abcdy")    # 差集 (difference_update)
>>> s
set(['x'])   

>>> s = set("abx")
>>> s ^= set("aby")    # 對稱差集 (symmetric_difference_update)
>>> s
set(['y', 'x'])  

集合和字典主鍵都必須是可哈希類型對象,但常用的 list、dict、set、defaultdict、OrderedDict 都是不可哈希的,僅有 tuple、frozenset 可用。

>>> hash([])
TypeError: unhashable type: 'list'

>>> hash({})
TypeError: unhashable type: 'dict'

>>> hash(set())
TypeError: unhashable type: 'set'

>>> hash(tuple()), hash(frozenset())
(3527539, 133156838395276)

如果想把自定義類型放入集合,需要保證 hash 和 equal 的結(jié)果都相同才能去重。

>>> class User(object):
...     def __init__(self, name):
...         self.name = name

>>> hash(User("tom")) # 每次的哈希結(jié)果都不同
279218517

>>> hash(User("tom"))
279218521

>>> class User(object):
...     def __init__(self, name):
...         self.name = name
...
...     def __hash__(self):
...         return hash(self.name)
...
...     def __eq__(self, o):
...         if not o or not isinstance(o, User): return False
...         return self.name == o.name

>>> s = set()

>>> s.add(User("tom"))
>>> s.add(User("tom"))

>>> s
set([<__main__.User object at 0x10a48d150>])

數(shù)據(jù)結(jié)構很重要,這幾個內(nèi)置類型并不足以完成全部工作。像 C、數(shù)據(jù)結(jié)構、常用算法這類基礎是每個程序開發(fā)人員都應該掌握的。

上一篇:字符串下一篇:迭代器