#增量賦值運算符+= 和*= 的表現取決於它們的第一個操作對象。+= 背後的特殊方法是__iadd__(用於「就地加法」)。但是如果一個類沒有實現這個方法的話,Python 會退一步調用__add__。可變序列一般都實現了__iadd__ 方法,因此+= 是就地加法。而不可變序列根本就不支持這個操作,對這個方法的實現也就無從談起。*=相對應的是__imul__
l = [1, 2, 3]
print(id(l))#2011053505408 剛開始時列表的ID。
l *= 2
print(id(l))#2011053505408 運用增量乘法後,列表的ID 沒變,新元素追加到列表上。
t = (1, 2, 3)
print(id(t))#2011054477376 元組最開始的ID。
t *= 2
print(id(t))#2011054746688 運用增量乘法後,新的元組被創建。
#對不可變序列進行重複拼接操作的話,效率會很低,因為每次都有一個新對象,而解釋器需要把原來對象中的元素先複製到新的對象裡,然後再追加新的元素.str 是一個例外,因為對字符串做+= 實在是太普遍了,所以CPython 對它做了優化。為str 初始化內存的時候,程序會為它留出額外的可擴展空間,因此進行增量操作的時候,並不會涉及複製原有字符串到新位置這類操作。
#一個關於+=的謎題
>>> t = (1, 2, [30, 40])
>>> t[2] += [50, 60]
"""
到底會發生下面4 種情況中的哪一種?
a. t 變成(1, 2, [30, 40, 50, 60])。
b. 因為tuple 不支持對它的元素賦值,所以會拋出TypeError 異常。
c. 以上兩個都不是。
d. a 和b 都是對的。
"""
"""
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40, 50, 60])
"""
由此可見a和b都是對的,使用extend()就不會提示異常
t = (1, 2, [30, 40])
t[2].extend([50, 60])
print(t)
import dis#Python代碼是先被編譯為Python字節碼後,再由Python虛擬機來執行Python字節碼(pyc文件主要就是用於存儲字節碼指令 的)。一般來說一個Python語句會對應若干字節碼指令,Python的字節碼是一種類似彙編指令的中間語言,但是一個字節碼指令並不是對應一個機器指 令(二進位指令),而是對應一段C代碼,而不同的指令的性能不同,所以不能單獨通過指令數量來判斷代碼的性能,而是要通過查看調用比較頻繁的指令的代碼來 確認一段程序的性能。dis模塊主要是用來分析字節碼的一個內置模塊,經常會用到的方法是dis.dis([bytesource]),參數為一個代碼塊,可以得到這個代碼塊對應的字節碼指令序列。
dis.dis('s[a] += b')
"""
1 0 LOAD_NAME 0 (s)
2 LOAD_NAME 1 (a)
4 DUP_TOP_TWO
6 BINARY_SUBSCR #將s[a] 的值存入TOS(Top Of Stack,棧的頂端)
8 LOAD_NAME 2 (b)
10 INPLACE_ADD #計算TOS += b。這一步能夠完成,是因為TOS 指向的是一個可變對象
12 ROT_THREE
14 STORE_SUBSCR #s[a] = TOS 賦值。這一步失敗,是因為s 是不可變的元組
16 LOAD_CONST 0 (None)
18 RETURN_VALUE
"""
#不要把可變對象放在元組裡面; 增量賦值不是一個原子操作。我們剛才也看到了,它雖然拋出了異常,但還是完成了操作; 查看Python 的字節碼並不難,而且它對我們了解代碼背後的運行機制很有幫助。
#list.sort 方法會就地排序列表,也就是說不會把原列表複製一份。這也是這個方法的返回值是None 的原因,提醒你本方法不會新建一個列表。在這種情況下返回None 其實是Python 的一個慣例:如果一個函數或者方法對對象進行的是就地改動,那它就應該返回None,好讓調用者知道傳入的參數發生了變動,而且並未產生新的對象。例如,random.shuffle 函數也遵守了這個慣例。
#內置函數sorted,它會新建一個列表作為返回值。這個方法可以接受任何形式的可迭代對象作為參數,甚至包括不可變序列或生成器。而不管sorted 接受的是怎樣的參數,它最後都會返回一個列表。
#不管是list.sort 方法還是sorted 函數,都有兩個可選的關鍵字參數:reverse如果被設定為True,被排序的序列裡的元素會以降序輸出(也就是說把最大值當作最小值來排序)。這個參數的默認值是False;key一個只有一個參數的函數,這個函數會被用在序列裡的每一個元素上,所產生的結果將是排序算法依賴的對比關鍵字。比如說,在對一些字符串排序時,可以用key=str.lower 來實現忽略大小寫的排序,或者是用key=len 進行基於字符串長度的排序。這個參數的默認值是恆等函數(identity function),也就是默認用元素自己的值來排序。
#可選參數key 還可以在內置函數min() 和max() 中起作用。另外,還有些標準庫裡的函數也接受這個參數,像itertools.groupby() 和heapq.nlargest() 等。
#Python 的排序算法——Timsort——是穩定的,意思是就算兩個元素比不出大小,在每次排序的結果裡它們的相對位置是固定的。
fruits = ['grape', 'raspberry', 'apple', 'banana']
print(sorted(fruits))#['apple', 'banana', 'grape', 'raspberry'] 新建了一個按照字母排序的字符串列表。
print(fruits)#['grape', 'raspberry', 'apple', 'banana'] 原列表並沒有變化。
print(sorted(fruits, reverse=True))#['raspberry', 'grape', 'banana', 'apple'] 按照字母降序排序。
print(sorted(fruits, key=len))#['grape', 'apple', 'banana', 'raspberry'] 新建一個按照長度排序的字符串列表。因為這個排序算法是穩定的,grape 和apple 的長度都是5,它們的相對位置跟在原來的列表裡是一樣的。
print(sorted(fruits, key=len, reverse=True))#['raspberry', 'banana', 'grape', 'apple'] 按照長度降序排序的結果。結果並不是上面那個結果的完全翻轉,因為用到的排序算法是穩定的,也就是說在長度一樣時,grape 和apple 的相對位置不會改變。
print(fruits) #['grape', 'raspberry', 'apple', 'banana'] 直到這一步,原列表fruits 都沒有任何變化。
print(fruits.sort()) #對原列表就地排序,返回值None 會被控制臺忽略。
print(fruits) #['apple', 'banana', 'grape', 'raspberry'] 此時fruits 本身被排序。
"""
數字 格式 輸出 描述
3.1415926 {:.2f} 3.14 保留小數點後兩位
3.1415926 {:+.2f} +3.14 帶符號保留小數點後兩位
-1 {:+.2f} -1.00 帶符號保留小數點後兩位
2.71828 {:.0f} 3 不帶小數
5 {:0>2d} 05 數字補零 (填充左邊, 寬度為2)
5 {:x<4d} 5xxx 數字補x (填充右邊, 寬度為4)
10 {:x<4d} 10xx 數字補x (填充右邊, 寬度為4)
1000000 {:,} 1,000,000 以逗號分隔的數字格式
0.25 {:.2%} 25.00% 百分比格式
1000000000 {:.2e} 1.00e+09 指數記法
13 {:>10d} 13 右對齊 (默認, 寬度為10)
13 {:<10d} 13 左對齊 (寬度為10)
13 {:^10d} 13 中間對齊 (寬度為10)
11 '{:b}'.format(11) 1011
'{:d}'.format(11) 11
'{:o}'.format(11) 13
'{:x}'.format(11) b
'{:#x}'.format(11) 0xb
'{:#X}'.format(11) 0XB 進位
^, <, > 分別是居中、左對齊、右對齊,後面帶寬度, : 號後面帶填充的字符,只能是一個字符,不指定則默認是用空格填充。
+ 表示在正數前顯示 +,負數前顯示 -; (空格)表示在正數前加空格
b、d、o、x 分別是二進位、十進位、八進位、十六進位。
"""
#用bisect來管理已排序的序列,bisect 模塊包含兩個主要函數,bisect 和insort,兩個函數都利用二分查找算法來在有序序列中查找或插入元素。
#用bisect來搜索,bisect(haystack, needle) 在haystack(乾草垛)裡搜索needle(針)的位置,該位置滿足的條件是,把needle 插入這個位置之後,haystack 還能保持升序。也就是在說這個函數返回的位置前面的值,都小於或等於needle 的值。其中haystack 必須是一個有序的序列。你可以先用bisect(haystack, needle) 查找位置index,再用haystack.insert(index,needle) 來插入新值。但你也可用insort 來一步到位,並且後者的速度更快一些。
#排序集合模塊(http://code.activestate.com/recipes/577197-sortedcollection/),模塊裡集成了bisect 功能,但是比獨立的bisect 更易用。
#在有序序列中用bisect 查找某個元素的插入位置
import bisect
import sys
HAYSTACK = [1, 4, 5, 6, 8, 12, 15, 20, 21, 23, 23, 26, 29, 30]
NEEDLES = [0, 1, 2, 5, 8, 10, 22, 23, 29, 30, 31]
ROW_FMT = '{0:2d} @ {1:2d} {2}{0:<2d}'
def demo(bisect_fn):
for needle in reversed(NEEDLES):
position = bisect_fn(HAYSTACK, needle) #用特定的bisect 函數來計算元素應該出現的位置。
offset = position * ' |' #利用該位置來算出需要幾個分隔符號。
print(ROW_FMT.format(needle, position, offset)) #把元素和其應該出現的位置列印出來。
if __name__ == '__main__':
if sys.argv[-1] == 'left': #根據命令上最後一個參數來選用bisect 函數。
bisect_fn = bisect.bisect_left
else:
bisect_fn = bisect.bisect
print('DEMO:', bisect_fn.__name__) #把選定的函數在抬頭列印出來。
print('haystack ->', ' '.join('%2d' % n for n in HAYSTACK))
demo(bisect_fn)
"""
DEMO: bisect_right
haystack -> 1 4 5 6 8 12 15 20 21 23 23 26 29 30
31 @ 14 | | | | | | | | | | | | | |31
30 @ 14 | | | | | | | | | | | | | |30
29 @ 13 | | | | | | | | | | | | |29
23 @ 11 | | | | | | | | | | |23
22 @ 9 | | | | | | | | |22
10 @ 5 | | | | |10
8 @ 5 | | | | |8
5 @ 3 | | |5
2 @ 1 |2
1 @ 1 |1
0 @ 0 0
"""
#bisect 的表現可以從兩個方面來調教。首先可以用它的兩個可選參數——lo 和hi——來縮小搜尋的範圍。lo 的默認值是0,hi的默認值是序列的長度,即len() 作用於該序列的返回值。
bisect 函數其實是bisect_right 函數的別名,後者還有個姊妹函數叫bisect_left。它們的區別在於,bisect_left 返回的插入位置是原序列中跟被插入元素相等的元素的位置,也就是新元素會被放置於它相等的元素的前面,而bisect_right 返回的則是跟它相等的元素之後的位置。
#bisect 可以用來建立一個用數字作為索引的查詢表格,比如說把分數和成績對應起來。根據一個分數,找到它所對應的成績#它們可以在很長的有序序列中作為index 的替代,用來更快地查找一個元素的位置。
import bisect
def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'):
i = bisect.bisect(breakpoints, score)
return grades[i]
print([grade(score) for score in [33, 99, 77, 70, 89, 90, 100]])#['F', 'A', 'C', 'C', 'B', 'A', 'A']
#insort(seq, item) 把變量item 插入到序列seq 中,並能保持seq 的升序順序。
#insort 可以保持有序序列的順序
import bisect
import random
SIZE=7
random.seed(1729)
my_list = []
for i in range(SIZE):
new_item = random.randrange(SIZE*2)
bisect.insort(my_list, new_item)
print('%2d ->' % new_item, my_list)
"""
10 -> [10]
0 -> [0, 10]
6 -> [0, 6, 10]
8 -> [0, 6, 8, 10]
7 -> [0, 6, 7, 8, 10]
2 -> [0, 2, 6, 7, 8, 10]
10 -> [0, 2, 6, 7, 8, 10, 10]
"""
#new_item = random.randrange(SIZE * 2)返回的隨機數列一直是相同不變的,後來發現random.randrange()產生的隨機數之所以會固定以因為random.seed()這個方法的原因。
#insort 跟bisect 一樣,有lo 和hi 兩個可選參數用來控制查找的範圍。它也有個變體叫insort_left,這個變體在背後用的是bisect_left。
#random.seed() 會改變隨機生成器的種子;傳入的數值用於指定隨機數生成時所用算法開始時所選定的整數值,如果使用相同的seed()值,則每次生成的隨機數都相同;如果不設置這個值,則系統會根據時間來自己選擇這個值,此時每次生成的隨機數會因時間的差異而有所不同。
import random
random.seed(1)
for i in range(1, 9):
print(random.randrange(10), end=' ')#2 9 1 4 1 7 7 7
print('\n')
for i in range(1, 9):
random.seed(1)
print(random.randrange(10), end=' ')#2 2 2 2 2 2 2 2
print('\n')
random.seed(1)
print(random.randrange(10), end=' ')#2
print('\n')
for i in range(1, 9):
print(random.randrange(10), end=' ')#9 1 4 1 7 7 7 6
#random.seed()的返回值是None;但是我覺得他會隱藏的構建一個類似列表的數據結構,這個列表的長度應該是特別大的(我曾將循環次數加到6000次發現返回的結果依舊是相同的),根據你在seed()中傳入的參數不同,seed會構建一個元素不同的列表,而random.randrange()會迭代這個列表,按照列表的順序將每個元素依次提出來作為隨機數算法的初始值。這也就是為什麼在例子1中設置了一個seed()後每次返回的隨機序列都是相同的,以在例子3的結果和例子1的結果也是相同的。
#而當我把seed()放進循環中去的時候,依舊會構建一個list,只不過list在循環裡面,每次都會被重置,而當random.randrange()每次去迭代的時候由於list一直被循環重置,所以random.randrange()在迭代列表時的指針也就會一直被重定向到首元素,也就一直去使用list中的第一個元素作為隨機數算法的開始整數,因此得到的隨機數序列就一直是基於list中的第一個元素求得的隨機數算法的值2。