按照用途不同,Python 內(nèi)置類型可分為 "數(shù)據(jù)" 和 "程序" 兩大類。
數(shù)據(jù)類型:
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ù)是虛擬機特殊照顧對象:
看看 "小數(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ù)。
字符串常量定義簡單自由,可以是單引號、雙引號或三引號。但我個人建議用雙引號表示字符串,用單引號表示字符,和其他語言習慣保持一致。
>>> "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)換方法。
>>> 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ù)組或鏈表。
創(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)上有很多不同之處。
在編碼中,應該盡可能用元組代替列表。除內(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)。
創(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ā)人員都應該掌握的。