1. 程式人生 > 實用技巧 >Python拾遺(2)

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

包括bytesstr。位元組的例項包含8個原生位元值,字串例項是由Unicode字元組成

py2

包括strunicode。字串代表原生的8位元,而unicode

Unicode字元組成。

因此程式碼中,最核心的部分應該使用Unicode字元型別,即py3中使用strpy2中使用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碼字元的時候,unicodestr例項看起來是一致的。因此可以採用:
    • +運算符合並strunicode
    • 可以使用等價或者不等價運算子來比較strunicode例項
    • 可以使用unicode來替換像%s這種字串中的格式化佔位符

然而在py3中,bytesstr的例項是不可能等價的。

  • 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檔案。因此,可以採用py2py3中都通用的方法:

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之類的函式。
  • 總是使用wbrb模式來處理檔案。

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

如果需要將自定義型別放入集合中,需要保證hashequal的結果都相同才行:

# -*- 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)