第二章--序列構成的數組

第二章–序列構成的數組

2.1 python內置序列類型

  • 按存儲類型來分
容器系列 list、tuple、collections.deque 能存放不同類型的數據
扁平系列 str、bytes、bytearray、memoryview、array.array 只能存放特定的一種類型的數據


容器系列存放的是對象的引用,扁平系列存放的是值,是一段連續的內存空間
  • 按能否被修改來分
可變序列 list、bytearray、array.array、collections.deque 和 memoryview
不可變序列 tuple、str 和 bytes
                             
 
 Sequence是不可變序列,MutableSequence是可變序列,圖中列舉了兩者的一些方法

2.2 生成列表與生成器表達式

生成列表略 把列表推到的[]改成()就成了生成器表達式,前者返回整個列表,後者返回生成器

2.3 元祖與拆包

  • 拆包(unpacking)
    
  1. 拆包可以用於任何可迭代對象上,但對字典進行拆包智能拿到key值
  2. 拆包時要注意變量數目左右要一直
  3. *arr 可以對arr進行拆包 (命令行下不能用)
  4. 函數中有一種特殊的拆包fun(a,*args,**kargs) args是元祖,接受多餘的無命名參數,kargs是字典,接受多餘的命名參數
  5. 可以用*來接收剩下的元素
a,b,*rest=range(5) #0 1 [2, 3, 4]
a,*rest,b,c=range(5)# 0 [1, 2] 3 4
  • 兩位數交換可以用 a,b=b,a
  • 可以用collections.namedtuple代替元祖
    namedtuple的一些使用函數 _fields _make _asdict
from collections import namedtuple

Student=namedtuple('Student',['name','grader'])

stu=Student('jason',2)
stu.name #jason
Student._fields # ('name', 'grader')
stu1=Student._make(('mike',3)) # Student(name='mike', grader=3)
stu1._asdict() # OrderedDict([('name', 'mike'), ('grader', 3)])

2.4 切片

  • 多維切片
    []運算符中可以使用逗號分開的多個索引或者切片,numpy就用了這個特性
    二維的 numpy.ndarray 就可以用 a[i, j] 這種形式來獲取,抑或是用 a[m:n, k:l]
    的方式來得到二維切片
    對象的特殊方法 __getitem__ 和__setitem__ 需要以元組的形式來接收 a[i, j] 中的索
    引。
    python內置的序列類型都是一維的,只支持單一索引

  • 給切片賦值,等式右邊必須是一個可迭代對象

arr=[1,2,3,4,5,6]
arr[2:4]=[999]*3
print(arr)
arr[2:4]=[888]
print(arr)
arr[2::3]=[777,666]
print(arr)
arr[2:3]=777 # raise exception
#[1, 2, 999, 999, 999, 5, 6]
# [1, 2, 888, 999, 5, 6]
# [1, 2, 777, 999, 5, 666]

2.5 對序列使用+和*

對序列使用+ 和* 不會修改原來的序列進行修改,會返回新的序列結果
注意嵌套列表的重複引用問題

arr=[[0]*3]*3
arr[0][0]=1
print(arr) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]
arr=[[0]*3 for i in range(3)]
arr[0][0]=1
print(arr) # [[1, 0, 0], [0, 0, 0], [0, 0, 0]]

2.6 序列的增量賦值

增量賦值運算符 += 和 = 的表現取決於它們的第一個操作對象
+=跟
=的概念是一樣的,只介紹+=
+= 背後的特殊方法是__iadd__ 如果一個類調用+=時沒有實現iadd,python會調用它的add
ex: a+=b 如果a類沒有實現iadd方法,就會調用add方法


class ClassA():
    def __init__(self,num=1):
        self.num=num
    def __add__(self, other):
        return ClassA(self.num+other)
    # def __iadd__(self, other):
    #     self.num+=other
    #     return self

class ClassB():
    def __init__(self,num=1):
        self.num=num
    def __add__(self, other):
        return ClassB(self.num+other)

    def __iadd__(self, other):
        self.num+=other
        return self

#a 只實現了add方法,可以看到調用a+=1前後a的id不一樣,說明調用了add方法
a=ClassA()
print(a.num, id(a))
a+=1
print(a.num, id(a))
# 1 2335603349376
# 2 2335603349488

#b實現了iadd 所以直接用iadd的方法,沒有用add,所以b+=1前後id一致
b=ClassB()
print(b.num,id(b))
b+=1
print(b.num,id(b))
# 1 2335603349432
# 2 2335603349432

對不可變序列進行拼接操作效率會很低 比如對元祖t 執行t*=2 t+=(2,3)不會直接在t內進行修改,而是計算得出結果保存到新變量,再把新變量賦值給t 並沒有就地賦值
但str是一個例外,str是不可變序列,但經常有s+=‘abc’這種操作,所以CPython進行了優化,讓str支持就地賦值,在str初始化內存時留出額外的內存空間,這樣可以一定程度上避免str的移動

一個很神奇的問題
在這裏插入圖片描述
在這裏插入圖片描述
用dis模塊查看了s[a]+=b的字節碼,可以找到原因
在這裏插入圖片描述

從這個問題學到的經驗

  1. 不要把可變對象放到不可變序列中
  2. 用dis查看python字節碼對調試非常有幫助
  3. 不可變序列的增量賦值不是原子操作

list.sort方法和內置函數sorted

python一些對數據進行就地修改的函數會返回None值,提醒你不會返回新的數據,比如list.sort() random.shuttle()

sorted相反,它最終會返回一個排好序的新列表,它接受任何可迭代對象作爲參數,並一直返回新列表

兩個排序函數都有的常用參數有reverse跟key

bisect–二分查找算法

bisect模塊包含兩個主要函數bisect和insort,兩個函數都利用二分查找算法在有序序列中查找插入元素

bisect其實是bisect_right,還有對應的biset_left
bisect_left 返回的插入位置是原序列中跟被插入元素相等的元素的位置,
也就是新元素會被放置於它相等的元素的前面,而 bisect_right 返回的則是跟它相等的元素
之後的位置。這可能會導致不同,因爲兩個元素相等但不一定相同,比如1跟1.0

def grade(score,breakpoints=[60,70,80,90],grade='FDCBA'):
    import bisect
    i=bisect.bisect(breakpoints,score)
    return grade[i]

print(list(map(grade,[33, 99, 77, 70, 89, 90, 100])))#['F', 'A', 'C', 'C', 'B', 'A', 'A']

insort也有對應的insort_left,都實現對有序序列的插入後保持序列有序

###列表的其他替代品

  • array模塊
    如果我們只需要存儲一種類型的序列化數據,array.array比list更高效,array.array支持所有list相關的操作,並且提供更快的文件讀取方法如.frombytes .tofile
    創建array時需要提供類型碼,這個類型碼用於表示底層c語言的數據類型,數據存儲是連續的
import array

arr=array.array('i',[0,1,1,3])
print(arr) # array('i', [0, 1, 1, 3])
print(arr.typecode) # i
print(arr.itemsize) # 4
print(arr.buffer_info()) #(2050462494912, 4) 返回的是數組的開頭地址跟數據個數
print(arr.count(1))#1
arr.pop(3)
print(arr) # array('i', [0, 1, 1])
arr.remove(0)
print(arr) # array('i', [1, 1])
print(array.typecodes) #bBuhHiIlLqQfd

  • memoryview

memoryview() 函數返回給定參數的內存查看對象(Momory view)。所謂內存查看對象,是指對支持緩衝區協議的數據進行包裝,在不需要複製對象基礎上允許Python代碼訪問。
memoryview是內置函數,它能讓用戶在不復制內容的情況下操作同一個數組的不同切片
http://stackoverflow.com/questions/4845418/when-should-a-memoryview-be-used/

內存視圖其實是泛化和去數學化的 NumPy 數組。它讓你在不需要複製內容的前提下,
在數據結構之間共享內存。其中數據結構可以是任何形式,比如 PIL 圖片、SQLite
數據庫和 NumPy 的數組,等等。這個功能在處理大型數據集合的時候非常重要。

memoryview.cast能用不同的方式讀寫同一塊內存數據,而且內容字節不會移動

import array

numbers=array.array('h',[-2,-1,0,1,2]) #h表示短整型有符號整數 兩個字節一個數
memv=memoryview(numbers)
print(len(memv)) # 5
print(memv[0])# -2
memv_oct=memv.cast('B') # B表示無符號字符,一個字節
print(memv_oct.tolist()) # [254, 255, 255, 255, 0, 0, 1, 0, 2, 0]
memv_oct[5]=4 # 低字節的在數據前面,高字節在後面,所以index=5的是numbers[2]的高位字節
print(numbers) # array('h', [-2, -1, 1024, 1, 2])
  • 隊列
  1. 雙向隊列 collections.deque 線程安全
  2. queue模塊 線程安全 提供了Queue、LifoQueue 和 PriorityQueue 三者都有一個參數maxsize,限定隊列的大小,滿員時不會扔掉元素,而是會鎖住當前線程,等待其他線程移除了元素騰出位置
  3. multiprocessing模塊 實現了用於多進程通信的Queue,還有一個multiprocessing.JoinableQueue 允許項目的使用者發送通知通知隊列的生成者數據已經被處理
    用的是task_done函數
  4. asyncio 主要實現了協程層面的隊列 Queue、LifoQueue、PriorityQueue 和 JoinableQueue
  5. heapq 提供heappush heappop方法,讓用戶把可變序列當做堆序列或優先隊列來使用
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章