Python拾遺(2)
包括Python
中的常用資料型別。
int
在64
位平臺上,int
型別是64
位整數:
- 從堆上按需申請名為
PyIntBlcok
的快取區域儲存整數物件 - 使用固定陣列快取
[-5, 257]
之間的小數字,只需計算下標就能獲得指標 PyIntBlock
記憶體不會返還給作業系統,直至程序結束
>>> a = 15 >>> b = 15 >>> a is b True >>> a = 1000 >>> b = 1000 >>> a is b False >>> a = 257 >>> b = 257 >>> a is b False
根據第三點,如果使用range
建立巨大的數字列表,這就需要很大記憶體空間。但是換成xrange
,每次迭代之後數字物件被回收,其佔用記憶體空間出來並被複用,記憶體就不會暴漲。(僅指python2
)。
當超出int
限制時,會自動轉換成為long
。
float
在python2
中,/
預設返回整數,而python3
中預設返回浮點數。如果需要準確控制運算精度,有效位數和round
的結果,使用Decimal
代替。(但是建議往Decimal
傳入字串型的浮點數 -- 為什麼你需要少看垃圾部落格以及如何在Python裡精確地四捨五入
>>> from decimal import Decimal, ROUND_UP, ROUND_DOWN >>> float('0.1') * 3 == float('0.3') False >>> Decimal('0.1') * 3 == Decimal('0.3') True
string
不太熟的方法
splitlines()
: 按行分割find()
: 查詢lstrip()/rstrip()/strip()
: 剔除前後空格replace()
:替換字元expandtabs()
:將tab
替換成空格ljust()/rjust()/center()/zfill()
:填充
字元編碼
在計算機記憶體中,統一使用Unicode
編碼,當需要儲存到硬碟或者需要傳輸的時候,轉換為UTF-8
編碼。
py3
:
包括
bytes
和str
。位元組的例項包含8
個原生位元值,字串例項是由Unicode
字元組成
py2
:
包括
str
和unicode
。字串代表原生的8
位元,而unicode
由Unicode
字元組成。
因此程式碼中,最核心的部分應該使用Unicode
字元型別,即py3
中使用str
,py2
中使用unicode
。這兩種字元型別沒有相關聯的二進位制編碼(原生的8
個位元值),因此如果要將Unicode
轉換為二進位制資料,應該使用encode
方法。而反過來,如果要二進位制編碼轉化為Unicode
字元,必須使用decode
方法。
py3
中的寫法:
def to_str(bytes_or_str):
if isinstance(bytes_or_str, bytes):
value = bytes_or_str.decode('utf-8')
else:
value = bytes_or_str
return value
def to_bytes(bytes_or_str):
if isinstance(bytes_or_str, str):
value = bytes_or_str.encode('utf-8')
else:
value = bytes_or_str
return value
py2
中的寫法:
def to_unicode(unicode_or_str):
if isinstance(unicode_or_str, str):
value = unicode_or_str.decode('utf-8')
else:
value = unicode_or_str
return value
def to_str(unicode_or_str):
if isinstance(unicode_or_str, unicode):
value = unicode_or_str.encode('utf-8')
else:
value = unicode_or_str
return value
需要注意的是兩大陷阱:
- 在
py2
中,當一個str
型別僅僅包含7個位元的ASCII
碼字元的時候,unicode
和str
例項看起來是一致的。因此可以採用:+
運算符合並str
和unicode
- 可以使用等價或者不等價運算子來比較
str
和unicode
例項 - 可以使用
unicode
來替換像%s
這種字串中的格式化佔位符
然而在
py3
中,bytes
和str
的例項是不可能等價的。
- 在
py3
中,涉及到檔案的處理操作,例如採用內建的open
函式,會預設以utf8
編碼,而在py2
中預設採用二進位制的形式編碼。舉個例子,在py3
的情況下,會報錯:
def open('/tmp/random.bin','w') as f:
f.write(os.urandom(10))
>>>
TypeError: must be str,not bytes
這是因為
py3
中對於open
函式新增了名為encoding
的引數,並且此引數預設值為utf-8
。因此,其對檔案的源的期望是包含了unicode
字串的str
例項,而不是包含了二進位制的bytes
檔案。因此,可以採用py2
與py3
中都通用的方法:
with open('/tmp/random.bin','wb') as f:
f.write(os.urandom(10))
畫重點
py3
中,bytes
是包含8
個位元位的序列,str
是包含unicode
的字串,它們不能同時出現在操作符>
或者+
中。py2
中,str
是包含8
個位元位的序列,而unicode
是包含unicode
的字串,二者可以同時出現在只包含7個位元的ASCII
碼的運算中。- 使用工具函式來確保程式輸入的資料是程式預期的型別,例如上面的
to_str
之類的函式。 - 總是使用
wb
和rb
模式來處理檔案。
string.ascii_letters/digits
使用string
類的ascii_letters
或者digits
可以獲得大小寫字元,以及數字,避免自己寫迴圈生成:
>>> import string
>>> string.ascii_letters
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
>>> type(string.ascii_letters)
<class 'str'>
>>> string.digits
'0123456789'
池化
使用intern()
可以把執行期動態生成的字串池化:
>>> s = "".join(['a', 'b', 'c'])
>>> s is "abc"
False
>>> intern(s) is "abc"
True
dict
popitem()
隨機返回並刪除字典中的一對鍵值
setdefault(key, default=None)
如果字典中包含有給定鍵,則返回鍵對應的值,否則返回為該鍵設定的值。
fromkeys(it, [initial])
返回以可迭代物件
it
裡的元素為鍵值的字典,如果有initial
引數,就把它作為這些鍵對應的值,預設是None
In [104]: info = {}.fromkeys(['name', 'blog'])
In [105]: info
Out[105]: {'blog': None, 'name': None}
In [106]: info = {}.fromkeys(['name', 'blog'], ['angel', 'jay'])
In [107]: info
Out[107]: {'blog': ['angel', 'jay'], 'name': ['angel', 'jay']}
In [110]: info = {}.fromkeys(['name', 'blog', 'test'], 'angel')
In [111]: info
Out[111]: {'blog': 'angel', 'name': 'angel', 'test': 'angel'}
update
使用指定鍵值對更新字典
可以採用key=value
的形式:
In [112]: info.update(blog='jay')
In [113]: info
Out[113]: {'blog': 'jay', 'name': 'angel', 'test': 'angel'}
也可以採用{key:value}
的形式:
In [114]: info.update({'test':'album'})
In [115]: info
Out[115]: {'blog': 'jay', 'name': 'angel', 'test': 'album'}
也可以使用tuple
的形式:
In [119]: info.update((('name', 'unkown'),('test', 'secret')))
In [120]: info
Out[120]: {'blog': 'jay', 'name': 'unkown', 'test': 'secret'}
對於大字典,呼叫keys()
、values()
、items()
會同樣構造巨大的列表,因此可以使用迭代器(iterkeys()
、itervalues()
、iteritems()
)減少記憶體開銷。
>>> d = {"a":1, "b":2}
>>> d.iterkeys()
<dictionary-keyiterator object at 0x7fde6e70b368>
>>> d.itervalues()
<dictionary-valueiterator object at 0x7fde6e70b3c0>
>>> d.iteritems()
<dictionary-itemiterator object at 0x7fde6e70b368>
>>> for k, v in d.iteritems():
... print k, v
...
a 1
b 2
檢視
判斷兩個字典間的差異,除了使用Counter
,也可以使用檢視。
>>> d1 = dict(a=1, b=2)
>>> d2 = dict(b=2, c=3)
>>> d1 & d2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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
set([('a', 1)])
>>> v1 ^ v2
set([('a', 1), ('c', 3)])
檢視會和字典同步變更:
>>> 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)])
collections.defaultdict
在例項化一個defaultdict
的時候,需要給構造方法提供一個可呼叫物件或者無引數函式,這個可呼叫物件會在__getitem__
(例如dd
是個defaultdict
,當dd[k]
時會呼叫此方法)碰到找不到的鍵的時候被呼叫,讓__getitem__
返回某種預設值。如果在建立defaultdict
的時候沒有指定可呼叫物件,查詢不存在的鍵會觸發KeyError
(這是由於__missing__
方法的緣故)。
使用一個型別初始化,當訪問鍵值不存在時,預設值為該型別例項。
>>> dd = defaultdict(list)
>>> dd['foo']
[]
>>> cc = defaultdict(dict)
>>> cc['foo']
{}
也可以使用不帶引數的可呼叫函式進行初始化,當鍵值沒有指定的時候,可以採用該函式進行初始化
>>> import collections
>>> def default_factory():
... return 'default value'
...
>>> d = collections.defaultdict(default_factory, foo='bar')
>>> print('d:', d)
d: defaultdict(<function default_factory at 0x7f2c39c04378>, {'foo': 'bar'})
>>> print(d['foo'])
bar
>>> print(d['bar'])
default value
>>> def zero():
... return 0
...
>>> ff = defaultdict(zero)
>>> ff['foo']
0
簡化為:
>>> ee = defaultdict(lambda : 0)
>>> ee['foo']
0
OrderedDict
字典是雜湊表,預設迭代是無序的,如果需要元素按照新增順序輸出結果,可以使用OrderedDict
。
>>> from collections import OrderedDict
>>> od = OrderedDict()
>>> od["a"] = 1
>>> od["c"] = 3
>>> od["b"] = 2
>>> for k, v in od.items(): print k, v
...
a 1
c 3
b 2
>>> od.popitem()
('b', 2)
>>> od.popitem()
('c', 3)
>>> od.popitem()
('a', 1)
因為字典是有序的,因此當使用
popitem
時,不再是隨機彈出
set
set
方法的pop
也是隨機彈出的。集合和字典的主鍵都必須是可雜湊型別物件。
>>> a = frozenset('abc')
>>> a
frozenset(['a', 'c', 'b'])
>>> a.add('d')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'frozenset' object has no attribute 'add'
>>> b = set('abc')
>>> b.add('d')
>>> b
set(['a', 'c', 'b', 'd'])
如果需要將自定義型別放入集合中,需要保證hash
和equal
的結果都相同才行:
# -*- coding: utf-8 -*-
class User(object):
def __init__(self, name):
self.name = name
def __hash__(self):
return hash(self.name)
def __eq__(self, other):
cls = type(self)
if not other or not isinstance(other, cls):
return False
return self.name == other.name
s = set()
s.add(User('tom'))
s.add(User('tom'))
print s
list
對於頻繁增刪元素的大型列表,應該考慮使用連結串列或者迭代器建立列表物件的方法(itertools
)。某些時候,可以考慮用陣列(array
)代替列表,但是它只能放指定的資料型別:
>>> import array
>>> a = array.array("l", range(10)) # 建立一個long型別的陣列
>>> a
array('l', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> a.tolist() #轉化為list
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> a = array.array("c")
>>> a.fromstring("abc")
>>> a
array('c', 'abc')
>>> a.fromlist(list("def"))
>>> a
array('c', 'abcdef')
>>> a.extend(array.array("c", "xyz"))
>>> a
array('c', 'abcdefxyz')
向有序列表插入元素
>>> l = ["a", "d", "c", "e"]
>>> l.sort()
>>> bisect.insort(l, "b")
>>> l
['a', 'b', 'c', 'd', 'e']
Tuple
在對tuple
進行比較的時候,Python
會優先比較元組中下標為0
的元素,然後依次遞增。有個很神奇的例子:
>>> values = [1,5,3,9,7,4,2,8,6]
>>> group = [7, 9]
>>> def sort_priority(values, group):
... def helper(x):
... if x in group:
... return (0, x)
... return (1, x)
... values.sort(key=helper)
...
>>> sort_priority(values, group)
>>> print(values)
[7, 9, 1, 2, 3, 4, 5, 6, 8]
namedtuple
需要兩個引數,一個是類名,另一個是類的各個欄位的名字。後者可以是由數個字串組成的可迭代物件,或者是由空格分隔開的欄位名組成的字串。
繼承自
tuple
的子類,建立的物件擁有可以訪問的屬性,但是仍然是隻讀的。
>>> from collections import namedtuple
>>> TPoint = namedtuple('TPoint', ['x', 'y'])
>>> p = TPoint(x=10, y=10)
>>> p.x, p.y
(10, 10)
也可以使用
TPoint = namedtuple('TPoint', 'x y')
這種格式
- 將資料變為
namedtuple
類:
>>> TPoint = namedtuple('TPoint', ['x', 'y'])
>>> t = [11 , 22]
>>> p = TPoint._make(t)
>>> p
TPoint(x=11, y=22)
>>> p.x
11
- 如果要進行更新,需要呼叫方法
_replace
:
>>> p
TPoint(x=10, y=10)
>>> p.y = 33
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: can't set attribute
>>> p._replace(y=33)
TPoint(x=10, y=33)
- 將字典資料轉換成
namedtuple
:
>>> p = {'x': 44, 'y': 55}
>>> p = TPoint(**p)
>>> p
TPoint(x=44, y=55)