成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Python學(xué)習(xí)之路21-序列構(gòu)成的數(shù)組

ralap / 2010人閱讀

摘要:第行把具名元組以的形式返回。對(duì)序列使用和通常號(hào)兩側(cè)的序列由相同類型的數(shù)據(jù)所構(gòu)成當(dāng)然不同類型的也可以相加,返回一個(gè)新序列。從上面的結(jié)果可以看出,它雖拋出了異常,但仍完成了操作查看字節(jié)碼并不難,而且它對(duì)我們了解代碼背后的運(yùn)行機(jī)制很有幫助。

《流暢的Python》筆記。
接下來的三篇都是關(guān)于Python的數(shù)據(jù)結(jié)構(gòu),本篇主要是Python中的各序列類型
1. 內(nèi)置序列類型概覽

Python標(biāo)準(zhǔn)庫用C實(shí)現(xiàn)了豐富的序列類型,可分為兩大類:

容器序列:list,tuplecollections.deque等這些序列能存放不同類型的數(shù)據(jù)。

扁平序列:str,bytesbytearray,memoryviewarray.array等,這些序列只能容納一種類型。

容器序列存放的是它們所包含的任意類型的對(duì)象的引用,而扁平序列存放的是值而不是引用。即,扁平序列其實(shí)是一段連續(xù)的內(nèi)存空間,更加緊湊。

序列類型還可以按能否被修改來分來:

可變序列(MutableSequence):list,bytearrayarray.array,collections.dequememoryview

不可變序列(Sequence):tuple,strbyte。

以下是這兩大類的繼承關(guān)系:

雖然Python中內(nèi)置的序列類型并不是直接從SequenceMutableSequence這兩個(gè)抽象基類繼承而來,但了解這些基類可以總結(jié)出那些完整的序列類型包含了哪些功能,以及將上述兩種分類方式融會(huì)貫通。

下面我們從最常用的列表(list)開始。

2. 列表推導(dǎo)和生成器表達(dá)式

列表推導(dǎo)(list comprehension,簡稱listcomps)是構(gòu)建列表的快捷方式,而生成器表達(dá)式(generator expression, 簡稱genexps)則可以用來創(chuàng)建其它任何類型的序列。

有時(shí)候,比起用for循環(huán),列表推導(dǎo)可能會(huì)更簡單可讀。通常的原則是,只用列表推導(dǎo)來創(chuàng)建新的列表,并且盡量保持簡短。如果列表推導(dǎo)的代碼超過了兩行,應(yīng)該考慮是不是得用for循環(huán)重寫,不過這個(gè)度得自己把握。(句法提示:Python會(huì)忽略[],{},()中的換行,所以可以省略不太好看的換行符)

注意:在Python3中,列表推導(dǎo)、生成器表達(dá)式,以及和它們很相似的集合(set)推導(dǎo)和字典(dict)推導(dǎo)都有了自己的局部作用域,不會(huì)影響外部的同名變量(Python2中則可能會(huì)影響),如下:

>>> x = "a"
>>> test = [x for x in "ABC"]
>>> x
"a"   # 在Python2中,該結(jié)果則可能是 "C"
2.1 列表推導(dǎo)同filtermap比較

列表推導(dǎo)可以過濾或加工一個(gè)序列或其他可迭代類型中的元素,然后生成一個(gè)新列表。而Python內(nèi)置的filtermap函數(shù)組合起來也能達(dá)到這一效果(一般需要借助lambda表達(dá)式),但可讀性卻比不上列表推導(dǎo),比如下面的代碼:

>>> symbols = "ABCDEFG"
>>> ascii = [ord(s) for s in symbols if ord(s) > 66]
>>> ascii
[67, 68, 69, 70, 71]
>>> ascii = list(filter(lambda c: c > 66, map(ord, symbols)))
>>> ascii
[67, 68, 69, 70, 71]

原本以為map/filter組合起來會(huì)比列表推導(dǎo)快一些,但有測試證明該結(jié)論不一定成立。對(duì)于map, filter的詳細(xì)介紹將放在后面的文章中。

2.2 笛卡爾積

簡單說就是簡化嵌套for循環(huán),例子如下:

colors = ["black", "white"]
sizes = ["S", "M", "L"]
tshirts = [(color, size) for color in colors for size in sizes]

tshirts_for = [] # 最后它的內(nèi)容等價(jià)于上面的tshirts
for color in colors:
    for size in sizes:
        tshirts_for.append((color, size))

列表推導(dǎo)的作用只有一個(gè):生成列表。如果想生成其他類型的序列,則需要使用生成器表達(dá)式。

2.3 生成器表達(dá)式

雖然也可以用列表推導(dǎo)式來初始化元組,數(shù)組或其他序列類型,但生成器表達(dá)式是更好的選擇,因?yàn)樯善鞅磉_(dá)式背后遵循了迭代器協(xié)議,可以逐個(gè)生成元素(可節(jié)省內(nèi)存),而不是一次性生成所有元素。

生成器表達(dá)式語法跟列表推導(dǎo)差不多,只是把方括號(hào)換成了圓括號(hào)而已,如下:

>>> symbols = "ABCDEFG"
>>> tuple(ord(symbol) for symbol in symbols)  # ①
(65, 66, 67, 68, 69, 70, 71)
>>> import array
>>> array.array("I", (ord(symbol) for symbol in symbols))  # ②
array("I", [65, 66, 67, 68, 69, 70, 71])

①如果生成器表達(dá)式是一個(gè)函數(shù)調(diào)用過程中的唯一參數(shù),則可不加括號(hào)將其圍起來;

②array的構(gòu)造方法需要兩個(gè)參數(shù),因此括號(hào)是必需的。

下面用生成器表達(dá)式改寫上面的笛卡爾積代碼:

colors = ["black", "white"]
sizes = ["S", "M", "L"]
for tshirt in ("%s %s" % (c, s) for c in colors for s in sizes):
    print(tshirts)

# 結(jié)果:
black S
black M
black L
white S
white M
white L

生成器表達(dá)式逐個(gè)生成元素,不會(huì)一次性生成一個(gè)含有6個(gè)元素的列表。關(guān)于生成器表達(dá)式的工作原理將在后面的文章中介紹。

3. 元組

元組除了用作不可變的列表,它還可以用于沒有字段名的記錄,比如坐標(biāo),身份信息等,這里不再舉例。

3.1 元祖拆包

此概念之前涉及過,這里將其總結(jié)一下:

# 平行賦值
a, b = ("test1", "test2")
# 不用中間變量交換兩個(gè)變量的值
b, a = a, b
# *號(hào)運(yùn)算將可迭代對(duì)象拆開作為函數(shù)參數(shù)
t = (20, 8)
divmod(*t)  # 該函數(shù)的意思是: 20 ÷ 8 = 2 …… 4, 函數(shù)返回商和余數(shù)的元組
# 用*來處理剩下的元素,Python3支持
a, b, *rest = range(5)  # rest的值為[2, 3, 4]
a, b, *rest = range(3)  # rest的值為[2]
a, b, *rest = range(2)  # rest的值為[]
# 在平行賦值中,*前綴只能用在一個(gè)變量前,但該變量可在任意位置
>>> a, *body, c, d = range(5) # 值依次為 0, [1, 2], 3, 4
>>> *head, b, c, d = range(5) # 值依次為 [0, 1], 2, 3, 4
3.2 嵌套元組拆包

接受表達(dá)式的元組可以是嵌套式的,例如(a, b, (c, d)),只要這個(gè)接受元組的嵌套結(jié)構(gòu)符合表達(dá)式本身的嵌套結(jié)構(gòu),以下用嵌套元組來獲取經(jīng)緯度:

metro_areas = [
    ("Tokyo", "JP", 36.933, (35.689722, 139.691667)),
    ("Delhi NCR", "IN", 21.935, (28.613889, 77.208889)),
    ("Mexico City", "MX", 20.142, (19.433333, -99.133333)),
    ("New York-Newark", "US", 20.104, (40.808611, -74.020386)),
    ("Sao Paulo", "BR", 19.649, (-23.547778, -46.635833)),
]

print("{:15} | {:^9} | {:^9}".format(" ", "lat.", "long."))
fmt = "{:15} | {:9.4f} | {:9.4f}"
# 把輸入元組的最后一個(gè)元素拆包到由變量構(gòu)成的元組中
for name, cc, pop, (latitude, longitude) in metro_areas:
    if longitude <= 0:
        print(fmt.format(name, latitude, longitude))
        
# 結(jié)果:
                |   lat.    |   long.  
Mexico City     |   19.4333 |  -99.1333
New York-Newark |   40.8086 |  -74.0204
Sao Paulo       |  -23.5478 |  -46.6358
3.3 具名元組(命名元組)

上篇中有所涉及。collections.namedtuple是一個(gè)工廠函數(shù),它可以創(chuàng)建一個(gè)帶字段名的元組和一個(gè)有名字的類——這個(gè)帶名字的類對(duì)調(diào)試程序有很大幫助。

namedtuple構(gòu)造的類的實(shí)例所消耗的內(nèi)存跟元組是一樣的,因?yàn)樽侄蚊即嬖趯?duì)于的類中。這個(gè)實(shí)例跟普通對(duì)象實(shí)例比起來要小一些,因?yàn)镻ython不會(huì)用__dict__來存放這些實(shí)例的屬性。

from collections import namedtuple

City = namedtuple("City", "name country population coordinates")
tokyo = City("Tokyo", "JP", 36.933, (35.689722, 139.691667))
print(tokyo)
print(tokyo.population)
print(tokyo[1])

print(City._fields)
LatLong = namedtuple("LatLong", "lat long")
delhi_data = ("Delhi NCR", "IN", 21.935, LatLong(28.613889, 77.208889))
delhi = City._make(delhi_data)
print(delhi._asdict())
for key, value in delhi._asdict().items():
    print(key + ":", value)

# 結(jié)果:
City(name="Tokyo", country="JP", population=36.933, coordinates=(35.689722, 139.691667))
36.933
JP
("name", "country", "population", "coordinates")
OrderedDict([("name", "Delhi NCR"), ("country", "IN"), ("population", 21.935),
             ("coordinates", LatLong(lat=28.613889, long=77.208889))])
name: Delhi NCR
country: IN
population: 21.935
coordinates: LatLong(lat=28.613889, long=77.208889)

第3行:創(chuàng)建一個(gè)具名元組需要兩個(gè)參數(shù),一個(gè)是類名,一個(gè)是類的各字段名。后者可以是由數(shù)個(gè)字符串組成的可迭代對(duì)象,或者是由空格分隔的字符串;

第6,7行:可通過字段名或位置來獲取一個(gè)字段的信息;

第9行:_fields屬性是一個(gè)包含這個(gè)類所有字段名的元組;

第12行:_make()通過接受一個(gè)可迭代對(duì)象來生成這個(gè)類的一個(gè)實(shí)例,它的作用跟City(*delhi_data)是一樣的。

第13行:_asdict()把具名元組以collections.OrderedDict的形式返回。

注意第10,27行!

3.4 作為不可變列表的元組

除了跟增減元素相關(guān)的方法外,元組支持列表的其他所有方法。還有一個(gè)例外就是元組沒有__reversed__方法,但這方法只是個(gè)優(yōu)化,reversed(my_tuple)這個(gè)方法在沒有__reversed__的情況下也是合法的。

4. 切片

切片在Python基礎(chǔ)中介紹了一些遍歷的基本操作,這里補(bǔ)充一些高級(jí)的用法。

4.1 切片賦值
>>> test = list(range(6))
>>> test
[0, 1, 2, 3, 4, 5]
# 指定步長賦值
>>> test[3::2] = [11, 22]
>>> test
[0, 1, 2, 11, 4, 22]
# 將列表變長(也可以變短)
>>> test[1:3] = [7, 8, 9]
>>> test
[0, 7, 8, 9, 11, 4, 22]
>>> test[1:3] = 100
Traceback (most recent call last):
  File "", line 1, in 
TypeError: can only assign an iterable
>>> test[1:3] = [100]
[0, 100, 9, 11, 4, 22]
4.2 有名字的切片

Python中有一個(gè)切片類(slice),可以用它創(chuàng)建切片對(duì)象:

temp = "adfadfadfadfafasdf"
TEST = slice(2, 8)  # 一般大寫
print(temp[TEST])

# 結(jié)果:
fadfad
4.3 多維切片和省略

[ ]運(yùn)算符中還可以使用以逗號(hào)分開的多個(gè)索引或者切片,比如第三方庫Numpy中就用到了這個(gè)特性,二維的numpy.ndarray就可以用a[i, j]來獲取值(這里的語法和C#一樣,相當(dāng)于C/C++中的a[i][j]),或者a[m:n, k:l]來獲得二維切片。要正確處理這種語法,對(duì)象的特殊方法__getitem____setitem__需要以元組的形式來接收a[i, j]中的索引,即,如果要得到a[i, j],Python會(huì)調(diào)用a.__getitem__((i, j))。關(guān)于多維切片的例子在本文后面演示。

省略(ellipsis)的寫法是三個(gè)英語句點(diǎn)(...),而不是Unicode碼位U+2026表示的半個(gè)省略號(hào)(和前面三個(gè)句點(diǎn)幾乎一模一樣)。省略在Python解釋器眼里是一個(gè)符號(hào),而實(shí)際上它是Elllipsis對(duì)象的別名,而Ellipsis對(duì)象又是ellipsis類的單一實(shí)例(ellipsis是類名,全小寫,而它的內(nèi)置實(shí)例寫作Ellipsis。這跟bool是小寫,而它的兩個(gè)實(shí)例TrueFalse是大寫一個(gè)道理)。它可以當(dāng)做切片規(guī)范的一部分,也可用在函數(shù)的參數(shù)列表中,如f(a,...,z),或a[i: ...]。在Numpy中,...用作多維數(shù)組切片的快捷方式,即x[i, ...]就是x[i, :, :, :]的縮寫。

筆者暫時(shí)還沒發(fā)現(xiàn)Python標(biāo)準(zhǔn)庫中有任何Ellipsis或者多維索引的用法。這些句法上的特性主要是為了支持用戶自定義類或者擴(kuò)展,Numpy就是一個(gè)例子。

5. 對(duì)序列使用+和*

通常+號(hào)兩側(cè)的序列由相同類型的數(shù)據(jù)所構(gòu)成(當(dāng)然不同類型的也可以相加),返回一個(gè)新序列。如果想把一個(gè)序列復(fù)制幾份再拼接,更快捷的做法是乘一個(gè)整數(shù):

>>> [1, 2] + [3]
[1, 2, 3]
>>> [1, 2] * 2
[1, 2, 1, 2]
>>> 5 * "abc"
"abcabcabcabcabc"

注意:這里有深淺復(fù)制的問題,如果在A * n這個(gè)語句中,序列A中的元素b是對(duì)其他可變對(duì)象的引用的話,則新序列中A2中的n個(gè)元素b1……bn都指向同一個(gè)位置,即對(duì)b1bn中任意一個(gè)賦值,都會(huì)影響其他元素。下面以一個(gè)創(chuàng)建多維數(shù)組的例子來說明這個(gè)情況(字符串是不可變對(duì)象,而列表是可變對(duì)象?。?/p>

正確的寫法:

board = [["_"] * 3 for i in range(3)]
print(board)
board[1][2] = "X"
print(board)
# 等價(jià)于:
board = []
for i in range(3):
    row = ["_"] * 3
    board.append(row)

# 結(jié)果:
[["_", "_", "_"], ["_", "_", "_"], ["_", "_", "_"]]
[["_", "_", "_"], ["_", "_", "X"], ["_", "_", "_"]]

錯(cuò)誤的寫法:

weird_board = [["_"] * 3] * 3
print(weird_board)
weird_board[1][2] = "X"
print(weird_board)
# 等價(jià)于:
weird_board = []
row = ["_"] * 3
for i in range(3):
    weird_board.append(row)

# 結(jié)果:
[["_", "_", "_"], ["_", "_", "_"], ["_", "_", "_"]]
[["_", "_", "X"], ["_", "_", "X"], ["_", "_", "X"]]
6. 序列的增量賦值

增量賦值運(yùn)算符+=*=的表現(xiàn)取決于它們的第一個(gè)操作對(duì)象,以+=為例。+=背后的特殊方法是__iadd__(用于“就地加法”),如果一個(gè)類沒有實(shí)現(xiàn)該方法,則會(huì)調(diào)用__add__。例如 a += b,如果a實(shí)現(xiàn)了__iadd__,則直接調(diào)用該方法,修改的是a,不會(huì)產(chǎn)生新對(duì)象,而如果沒有實(shí)現(xiàn)該方法,則會(huì)調(diào)用__add__,執(zhí)行的運(yùn)算實(shí)際是 a = a + b,該運(yùn)算會(huì)生成一個(gè)新變量,存儲(chǔ)a + b的結(jié)果,然后再把該新變量賦值給a。

總體來說,可變序列一般都實(shí)現(xiàn)了__iadd__,而不可變序列根本就不支持這個(gè)操作。對(duì)不可變序列執(zhí)行重復(fù)拼接操作的話,效率很低,因?yàn)槊看味紩?huì)生成新對(duì)象,而解釋器需要把原來對(duì)象中的元素先復(fù)制到新對(duì)象中,然后再追加新元素。但str是個(gè)例外,因?yàn)閷?duì)字符串做+=操作是在太普遍了,于是CPython對(duì)它做了優(yōu)化:str初始化時(shí),程序會(huì)為它預(yù)留額外的可擴(kuò)展空間,因此做增量操作時(shí)不會(huì)涉及復(fù)制原有字符串到新位置的操作。

一個(gè)關(guān)于+=的謎題

對(duì)于以下操作,大家猜想會(huì)得到什么樣的結(jié)果:

>>> t = (1, 2, [3, 4])
>>> t[2] += [5, 6]

它的結(jié)果是報(bào)錯(cuò),但t依然被改變了:

# 緊接上述代碼
Traceback (most recent call last):
  File "", line 1, in 
TypeError: "tuple" object does not support item assignment
>>> t
(1, 2, [3, 4, 5, 6])
# 如果是t[2].extend([5, 6])則不會(huì)報(bào)錯(cuò)

如果我們看Python表達(dá)式 s[a] += b的字節(jié)碼,便不難理解上述結(jié)果:

>>> import dis
>>> dis.dis("s[a] += b")
  1           0 LOAD_NAME                0 (s)
              2 LOAD_NAME                1 (a)
              4 DUP_TOP_TWO
              6 BINARY_SUBSCR
              8 LOAD_NAME                2 (b)
             10 INPLACE_ADD
             12 ROT_THREE
             14 STORE_SUBSCR
             16 LOAD_CONST               0 (None)
             18 RETURN_VALUE

從上述結(jié)果可以看出:

第6行:將s[a]的值存入TOS(Top Of Stack,棧頂);

第8行:計(jì)算TOS += b, 這一步能夠完成,因?yàn)?b>TOS指向一個(gè)可變對(duì)象;

第14行:s[a] = TOS,報(bào)錯(cuò),因?yàn)?b>s是個(gè)元組,不可變。

從上述操作可以得到3個(gè)教訓(xùn):

不要把可變對(duì)象放在元組中;

增量賦值不是一個(gè)原子操作。從上面的結(jié)果可以看出,它雖拋出了異常,但仍完成了操作;

查看Python字節(jié)碼并不難,而且它對(duì)我們了解代碼背后的運(yùn)行機(jī)制很有幫助。

7. 用bisect來管理已排序的序列

bisect模塊包含兩個(gè)主要函數(shù),bisectinsort,這兩個(gè)函數(shù)都利用二分查找算法在有序列表中查找或插入元素。

bisect用于查找元素的位置:biisect(haystack, needle)。它返回needlehaystack中的位置index,如果要插入元素,可以在找到位置后,再調(diào)用haystack.insert(index, new_ele),但也可以用bisect模塊中的insert直接插入,并且該方法速度更快。

Python的高產(chǎn)貢獻(xiàn)者Raymond Hettinger寫了一個(gè)排序集合模塊sortedcollection,該模塊集成了bisect功能,且比獨(dú)立的bisect更易用。

bisect需要注意兩點(diǎn):

兩個(gè)可選參數(shù)lohilo默認(rèn)值是0,hi默認(rèn)值是序列的長度,即len()作用域該序列的返回值。

bisect函數(shù)其實(shí)是bisect_right函數(shù)的別名,它返回的位置是與needle相等的元素的后一個(gè)位置,而它的兄弟函數(shù)bisect_left則返回的是與needle相等的元素的位置。

>>> import bisect
>>> test = [1, 2, 3, 4, 5, 6, 7]
>>> bisect.bisect(test,1)
1
>>> bisect.bisect_left(test,1)
0

相應(yīng)的,模塊中insort也有兩個(gè)版本,insortinsort_right的別名,它也有兩個(gè)可選參數(shù)lohiinsort_left的背后調(diào)用的就是bisect_left。

>>> bisect.insort(test, 1.0)
>>> test
[1, 1.0, 2, 3, 4, 5, 6, 7]
>>> bisect.insort_left(test, 1.0)
>>> test
[1.0, 1, 1.0, 2, 3, 4, 5, 6, 7]
8. 當(dāng)列表不是首選時(shí)

當(dāng)我們有特定的數(shù)據(jù)集時(shí),list并不一定是首選,比如存放1000萬個(gè)浮點(diǎn)數(shù),數(shù)組(array)的效率就要高很多,因?yàn)閿?shù)組的背后并不是float對(duì)象,而是數(shù)字的機(jī)器翻譯,也就是字節(jié)表述。這點(diǎn)和C語言中的數(shù)組一樣。再比如,如果要頻繁對(duì)序列做先進(jìn)先出的操作,deque(雙端隊(duì)列)的速度應(yīng)該會(huì)更快。

8.1 數(shù)組

如果需要一個(gè)只含數(shù)字的列表,array.array會(huì)比list更高效,它支持所有跟可變列表有關(guān)的操作,包括.pop.insert,.extend等。另外數(shù)組還支持從文件讀取和存入文件的更快的方法,比如.frombytes.tofile

數(shù)組跟C語言數(shù)組一樣精簡,創(chuàng)建一個(gè)數(shù)組需要指定一個(gè)類型碼,這個(gè)類型碼用來表示在底層的C語言應(yīng)該存放怎樣的數(shù)據(jù)類型,以下是array.array的操作例子:

from array import array
from random import random

print("

")
floats = array("d", (random() for i in range(10 ** 7)))
print(floats[-1])
with open("floats.bin", "wb") as fp:
    floats.tofile(fp)

floats2 = array("d")
with open("floats.bin", "rb") as fp:
    floats2.fromfile(fp, 10 ** 7)
print(floats2[-1])
print(floats2 == floats)

# 結(jié)果:
0.8220703930498271
0.8220703930498271
True

有人做過實(shí)驗(yàn),用array.fromfile從一個(gè)二進(jìn)制文件讀出1000萬個(gè)雙精度浮點(diǎn)數(shù)只需要0.1秒(筆者電腦有點(diǎn)年代了,達(dá)不到這個(gè)速度),速度是從文本文件里讀取的60倍,因?yàn)楹笳邥?huì)使用內(nèi)置的float方法把每一行文字轉(zhuǎn)換成浮點(diǎn)數(shù)。另外,array.tofile寫入二進(jìn)制文件也比寫入文本文件快7倍。另外,這1000萬個(gè)數(shù)的bin文件只占8千萬字節(jié),如果是文本文件的話,需要181515739字節(jié)。

另一個(gè)快速序列化數(shù)字類型的方法是使用pickle模塊,pickle.dump處理浮點(diǎn)數(shù)組的速度幾乎和array.tofile一樣快,而且pickle可以處理幾乎所有的內(nèi)置數(shù)字類型

8.2 內(nèi)存視圖memoryview

memoryview是個(gè)內(nèi)置類,它讓用戶在不復(fù)制內(nèi)存的情況下操作同一個(gè)數(shù)組的不同切片。memoryview的概念受到了Numpy的啟發(fā)。

內(nèi)存視圖其實(shí)是泛化和去數(shù)學(xué)化的Numpy數(shù)組。它讓你在不需要復(fù)制內(nèi)容的前提下,在數(shù)據(jù)結(jié)構(gòu)之間共享內(nèi)存。其中數(shù)據(jù)結(jié)構(gòu)可以是任何形式,比如PIL圖片、SQLite數(shù)據(jù)庫和Numpy數(shù)組等待。這個(gè)功能在處理大型數(shù)據(jù)集合的時(shí)候非常重要。

memoryview.cast的概念跟數(shù)組模型類似,能用不同的方式讀取同一塊內(nèi)存數(shù)據(jù),而且內(nèi)存字節(jié)不會(huì)隨意移動(dòng)。這有點(diǎn)類似于C語言的類型轉(zhuǎn)換。memoryview.cast會(huì)把同一塊內(nèi)存里的內(nèi)容打包成一個(gè)全新的memoryview對(duì)象返回。

下面這個(gè)例子精確地修改一個(gè)數(shù)組的某個(gè)字節(jié):

import array
# 16位二進(jìn)制整數(shù)
numbers = array.array("h", [-2, -1, 0, 1, 2])
memv = memoryview(numbers)
print(len(memv))
print(memv[0])
# 轉(zhuǎn)換成8位的無符號(hào)整數(shù)
memv_oct = memv.cast("B")
print(memv_oct.tolist())
# 這個(gè)坐標(biāo)剛好是第3個(gè)16位二進(jìn)制數(shù)的高位字節(jié)
memv_oct[5] = 4
print(numbers)

# 結(jié)果:
5
-2
[254, 255, 255, 255, 0, 0, 1, 0, 2, 0]
array("h", [-2, -1, 1024, 1, 2])
8.3 NumPy和SciPy

拼接這NumPy和SciPy提供的高階數(shù)組和矩陣操作,Python稱為科學(xué)計(jì)算應(yīng)用的主流語言。NumPy實(shí)現(xiàn)了多維同質(zhì)數(shù)組(homogeneous array)和矩陣,這些數(shù)據(jù)結(jié)構(gòu)不但能處理數(shù)字,還能存放其他由用戶定義的記錄。SciPy是基于NumPy的另一個(gè)庫,他提供了很多跟科學(xué)計(jì)算有關(guān)的算法,專為線性代數(shù)、數(shù)值積分和統(tǒng)計(jì)學(xué)而設(shè)計(jì)。SciPy的高校和可靠性歸功于背后的C和Fortran代碼,而這些跟計(jì)算有關(guān)的部分都源自于Netlib。SciPy把基于C和Fortran的工業(yè)級(jí)數(shù)學(xué)計(jì)算功能用交互式且高度抽象的Python包裝起來。

以下是一些NumPy二維數(shù)組的基本操作:

>>> import numpy
>>> a = numpy.arange(12)
>>> a
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])

>>> type(a)

# 數(shù)組a的維度
>>> a.shape
(12,)
# 手動(dòng)設(shè)置數(shù)組維度,3行4列
>>> a.shape = 3, 4
>>> a
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
# 第2行
>>> a[2]
array([ 8,  9, 10, 11])
# 第2行第1列元素
>>> a[2, 1]
9
# 第1列元素
>>> a[:, 1]
array([1, 5, 9])
# 轉(zhuǎn)置
>>> a.transpose()
array([[ 0,  4,  8],
       [ 1,  5,  9],
       [ 2,  6, 10],
       [ 3,  7, 11]])
# 全部數(shù)據(jù)乘2
>>> a *= 2
>>> a
array([[ 0,  2,  4,  6],
       [ 8, 10, 12, 14],
       [16, 18, 20, 22]])

NumPy也可讀取、寫入文件:

# 從文本文件中讀取數(shù)據(jù)
floats = numpy.loadtxt("filename.txt")
# 把數(shù)組存入后綴為.npy的二進(jìn)制文件,會(huì)自動(dòng)加后綴名
numpy.save("filesave", floats)
# 從.npy文件中讀取數(shù)據(jù),這次load方法利用了一種叫做內(nèi)存映射的機(jī)制,它讓
# 我們?cè)趦?nèi)存不足的時(shí)候仍可以對(duì)數(shù)組切片
floats2 = numpy.load("filesave.npy", "r+")

這兩個(gè)庫都異常強(qiáng)大,它們也是一些其他庫的基礎(chǔ),比如Pandas和Blaze數(shù)據(jù)分析庫。

8.4 雙向隊(duì)列和其他形式的隊(duì)列

利用.append.pop方法,可以將列表(list)變成棧和隊(duì)列。但刪除列表的第一個(gè)元素或在第一個(gè)元素前插入元素之類的操作會(huì)很耗時(shí),因?yàn)闀?huì)移動(dòng)數(shù)據(jù)。如果經(jīng)常要在列表兩端操作數(shù)據(jù),推薦使用collections.deque類(雙向隊(duì)列)。它是一個(gè)線程安全、可快速從兩端添加刪除元素的數(shù)據(jù)類型。下面是它的操作示范:

# maxlen是個(gè)可選參數(shù),表示隊(duì)列最大長度,該屬性一旦設(shè)定變不能修改
>>> dq = deque(range(10), maxlen=10)
>>> dq
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
# 隊(duì)列旋轉(zhuǎn)操作,接收參數(shù)n,當(dāng)n>0時(shí),隊(duì)列最右邊n個(gè)元素移動(dòng)到最左邊
# 當(dāng)n<0時(shí),隊(duì)列最左邊n個(gè)元素移動(dòng)到最右邊
>>> dq.rotate(3)
>>> dq
deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10)

>>> dq.rotate(-4)
>>> dq
deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], maxlen=10)
# 隊(duì)列左邊添加一個(gè)元素-1,由于隊(duì)列長10,所以元素0被刪除
>>> dq.appendleft(-1)
>>> dq
deque([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
# 隊(duì)列右邊添加三個(gè)元素,擠掉了最前面的三個(gè)元素
>>> dq.extend([11, 22, 33])
>>> dq
deque([3, 4, 5, 6, 7, 8, 9, 11, 22, 33], maxlen=10)
# 注意添加的順序
>>> dq.extendleft([10, 20, 30, 40])
>>> dq
deque([40, 30, 20, 10, 3, 4, 5, 6, 7, 8], maxlen=10)

該數(shù)據(jù)結(jié)構(gòu)還有許多其他操作,appendpopleft是原子操作,可在多線程中安全地使用,不用擔(dān)心資源鎖的問題。

8.5 Python標(biāo)準(zhǔn)庫中的隊(duì)列

queue:提供了同步(線程安全)類QueueLifoQueuePriorityQueue,不同的線程可以利用這些數(shù)據(jù)類型來交換信息。這三個(gè)類在隊(duì)列滿的時(shí)候不會(huì)丟掉舊元素,而是被鎖住,直到某線程移除了某個(gè)元素。這一特性讓這些類很適合用來控制活躍線程的數(shù)量。

multiprocessing:實(shí)現(xiàn)了自己的Queue,和queue.Queue類似,設(shè)計(jì)給進(jìn)程間通信用的。同時(shí)還有一個(gè)專門的multiprocessing.JoinableQueue類,該類讓任務(wù)管理變得方便。

asyncio:從Python3.4新增的包,包含Queue,LifoQueue,PriorityQueueJoinableQueue,這些類受queuemultiprocessing模塊的影響,但是為異步編程里的任務(wù)管理提供了專門的便利。

heapq:和上述三個(gè)模塊不同,它沒有隊(duì)列類,而是提供了heappushheappop方法,讓用戶可以把可變序列當(dāng)作堆隊(duì)列或者優(yōu)先隊(duì)列來使用。

9. 補(bǔ)充

Python入門教材往往會(huì)強(qiáng)調(diào)列表可以容納不同類型的元素,但實(shí)際上這樣做并沒有什么特別的好處。之所以用列表來存放東西,是期待在稍后使用它的時(shí)候,其中的元素能有一些共有的特性。Python3中,如果列表里的元素不能比較大小,則是不能對(duì)列表進(jìn)行排序的。元組則恰恰相反,它經(jīng)常用來存放不同類型的元素,這也符合它的本質(zhì),元組就是用作存放彼此之間沒有關(guān)系的數(shù)據(jù)的記錄。

list.sortsorted,maxmin函數(shù)的key參數(shù)是個(gè)很棒的設(shè)計(jì),相比于其他語言中雙參數(shù)比較函數(shù),這里的參數(shù)key只需提供一個(gè)單參數(shù)函數(shù)來提取或計(jì)算一個(gè)值作為比較大小的標(biāo)準(zhǔn)。說它更高效,是因?yàn)樵诿總€(gè)元素上,key函數(shù)只被調(diào)用一次。誠然,在排序的時(shí)候,Python總會(huì)比較兩個(gè)鍵(key),但那一階段的計(jì)算發(fā)生在C語言那一層,這樣會(huì)比調(diào)用用戶自定義的Python比較函數(shù)更快。key參數(shù)也能讓你對(duì)一個(gè)混有數(shù)字字符和數(shù)值的列表進(jìn)行排序,只需決定到底是將字符看做數(shù)值(數(shù)值排序),還是將數(shù)值看成字符(ASCII排序),即key到底是等于int還是等于str。

sortedlist.sort背后的排序算法是Timsort,它是一種自適應(yīng)算法,會(huì)根據(jù)原始數(shù)據(jù)的順序特點(diǎn)交替使用插入排序(數(shù)列基本有序時(shí))和歸并排序(沒什么規(guī)律時(shí)),以達(dá)到最佳效率。這樣的算法被證明是有效的,因?yàn)閬碜哉鎸?shí)世界的數(shù)據(jù)通常是有一定的順序特點(diǎn)的。Timsort在2002年的時(shí)候首次用在CPython中,自2009年起,Java和Android也開始使用這個(gè)算法。后來該算法被廣為人知,是因?yàn)樵贕oogle對(duì)Sun的侵權(quán)案中,Oracle把Timsort中的一些相關(guān)代碼作為了呈堂證供。Timsort的創(chuàng)始人是Tim Peters,一位高產(chǎn)的Python核心開發(fā)者,他也是“Python之禪”的作者之一。


迎大家關(guān)注我的微信公眾號(hào)"代碼港" & 個(gè)人網(wǎng)站 www.vpointer.net ~

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/44718.html

相關(guān)文章

  • Python學(xué)習(xí)之路24-一等函數(shù)

    摘要:函數(shù)內(nèi)省的內(nèi)容到此結(jié)束。函數(shù)式編程并不是一個(gè)函數(shù)式編程語言,但通過和等包的支持,也可以寫出函數(shù)式風(fēng)格的代碼。 《流暢的Python》筆記。本篇主要講述Python中函數(shù)的進(jìn)階內(nèi)容。包括函數(shù)和對(duì)象的關(guān)系,函數(shù)內(nèi)省,Python中的函數(shù)式編程。 1. 前言 本片首先介紹函數(shù)和對(duì)象的關(guān)系;隨后介紹函數(shù)和可調(diào)用對(duì)象的關(guān)系,以及函數(shù)內(nèi)省。函數(shù)內(nèi)省這部分會(huì)涉及很多與IDE和框架相關(guān)的東西,如果平時(shí)...

    wind3110991 評(píng)論0 收藏0
  • python學(xué)習(xí)筆記 序列

    內(nèi)置序列 容器序列 list, tuple, collections.deque等這些序列能存放不同類型的數(shù)據(jù) 扁平序列 str, byte, bytearray, memoryview, array.array, 這些序列只能容納一種類型數(shù)據(jù) 以上,容器序列存放的是他們所含任意類型對(duì)象的引用,而扁平序列存放的是值而不是引用 列表(list)是最基礎(chǔ)也是最重要的序列類型 列表推導(dǎo) >>> symb...

    godiscoder 評(píng)論0 收藏0
  • Python 進(jìn)階之路 (五) map, filter, reduce, zip 一網(wǎng)打盡

    摘要:另外,這些中的每一個(gè)都是純函數(shù),有返回值。例如,如果要計(jì)算整數(shù)列表的累積乘,或者求和等等基礎(chǔ)語法參數(shù)是連續(xù)作用于每一個(gè)元素的方法,新的參數(shù)為上一次執(zhí)行的結(jié)果,為被過濾的可迭代序列返回值最終的返回結(jié)果在中,是一個(gè)內(nèi)置函數(shù)。 簡潔的內(nèi)置函數(shù) 大家好,我又回來了,今天我想和大家分享的是Python非常重要的幾個(gè)內(nèi)置函數(shù):map,filter,reduce, zip。它們都是處理序列的便捷函數(shù)...

    ccj659 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<