日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > python >内容正文

python

3.Python3标准库--数据结构

發(fā)布時(shí)間:2023/12/20 python 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 3.Python3标准库--数据结构 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

?(一)enum:枚舉類型

import enum''' enum模塊定義了一個(gè)提供迭代和比較功能的枚舉類型。可以用這個(gè)為值創(chuàng)建明確定義的符號(hào),而不是使用字面量整數(shù)或字符串 '''

  

1.創(chuàng)建枚舉

import enum''' 可以使用定義一個(gè)類,繼承自Enum,來實(shí)現(xiàn)枚舉 '''class Color(enum.Enum):red = 1green = 2yellow = 3cyan = 4purple = 5# 此時(shí)我們可以直接通過類名來調(diào)用里面的元素,里面的元素有兩個(gè)屬性,一個(gè)是name,一個(gè)是value print(Color.red) # Color.red print(Color.red.name) # red print(Color.red.value) # 1

  

2.迭代

import enum''' 可以迭代處理枚舉的各個(gè)成員 '''class Color(enum.Enum):red = 1green = 2yellow = 3cyan = 4purple = 5for c in Color:print(c, c.name, c.value)'''Color.red red 1Color.green green 2Color.yellow yellow 3Color.cyan cyan 4Color.purple purple 5''' # 可以看到打印是由順序的,就是我們添加的順序

  

3.比較Enum

import enum''' 枚舉類型只能比較是否相等,不能進(jìn)行大小判斷 '''class Color(enum.Enum):red = 1green = 2yellow = 3cyan = 4purple = 5print(Color.red == Color.cyan) # False print(Color.red == Color.red) # True print(Color.red is Color.red) # True# 注意:如果進(jìn)行大小比較,會(huì)拋出一個(gè)TypeError try:Color.red > Color.cyan except TypeError as err:print(err) # '>' not supported between instances of 'Color' and 'Color'# 但如果我非要比較呢?那就不能繼承Enum了,需要繼承IntEnum class Color(enum.IntEnum):red = 1green = 2yellow = 3cyan = 4purple = 5# 會(huì)自動(dòng)將值進(jìn)行比較 print(Color.cyan < Color.purple) # True # 會(huì)自動(dòng)將值進(jìn)行相加 print(Color.red + Color.green) # 3

  

4.唯一枚舉值

import enum''' 有相同值得Enum成員會(huì)被處理為同一個(gè)對(duì)象的別名引用,別名可以避免Enum的迭代器中出現(xiàn)相同的值 '''class Color(enum.Enum):red = 1green = 2yellow = 3cyan = 4purple = 5black = 3for c in Color:print(c.name)'''redgreenyellowcyanpurple''' # 可以看到black并沒有被打印出來,因?yàn)樗蛓ellow的值一樣,所以將其當(dāng)做了yellow的別名 # 至于為什么是black被當(dāng)做了yellow的別名,而不是yellow被當(dāng)做black的別名,原因很簡(jiǎn)單,因?yàn)閎lack在下面 print(Color.yellow is Color.black) # True# 并且這里要提一點(diǎn),值是可以重復(fù)的,只是會(huì)被當(dāng)做別名,但是name是不能重復(fù)的,一旦重復(fù),必定報(bào)錯(cuò) # 那如果我想值也不能重復(fù)呢?很簡(jiǎn)單, 只需要加上一個(gè)裝飾器即可 try:@enum.uniqueclass Color(enum.Enum):red = 1green = 2yellow = 3cyan = 4purple = 5black = 3 except ValueError as err:print(err) # duplicate values found in <enum 'Color'>: black -> yellow

  

(二)collections:容器數(shù)據(jù)類型

import collections''' collections模塊包含除內(nèi)置類型list、dict和tuple等意外的其他容器數(shù)據(jù)類型 '''

1.ChainMap:搜索多個(gè)字典

from collections import ChainMap''' ChainMap類一個(gè)字典序列,并按照其出現(xiàn)的順序搜索以查找與鍵關(guān)聯(lián)的值。 ChainMap提供了一個(gè)很好的上下文容器,因?yàn)榭梢园阉醋龀梢粋€(gè)棧,棧增長(zhǎng)時(shí)發(fā)生變更,棧收縮時(shí)這些變更將被丟棄 ''' d1 = {"a": 12, "b": 22, "c": 33} d2 = {"b": 1, "c": 2, "d": 3} d = ChainMap(d1, d2) for k, v in d.items():print(k, v)'''b 22c 33d 3a 12'''# 可以看到打印的結(jié)果是無序的,而且如果多個(gè)字典中有相同的key,那么只保留第一次出現(xiàn)的key# 并且ChainMap有一個(gè)maps屬性,存儲(chǔ)了要搜索的映射列表。這個(gè)列表是可變的。所以可以直接增加新映射,或者改變?cè)氐捻樞蛞钥刂撇檎液透滦袨椤?print(d.maps) # [{'a': 12, 'b': 22, 'c': 33}, {'b': 1, 'c': 2, 'd': 3}]# 這是我們存儲(chǔ)的信息,如果在d.maps里面修改了,那么會(huì)怎么樣呢? print(d1) # {'a': 12, 'b': 22, 'c': 33} d.maps[0]["a"] = "yoyoyo" # 可以看到d.maps里面存儲(chǔ)的只是一個(gè)引用,因此改變之后會(huì)影響原來的結(jié)果 print(d1) # {'a': 'yoyoyo', 'b': 22, 'c': 33}# 那我如果改變了原來的值,會(huì)不會(huì)影響d.maps呢?顯然是可以的,畢竟同一個(gè)內(nèi)存地址嘛 d2["d"] = "我屮艸芔茻" print(d.maps) # [{'a': 'yoyoyo', 'b': 22, 'c': 33}, {'b': 1, 'c': 2, 'd': '我屮艸芔茻'}]

  

2.Counter:統(tǒng)計(jì)可散列的對(duì)象

?

from collections import Counter''' Counter是一個(gè)容器,可以計(jì)算出序列中每一個(gè)元素出現(xiàn)的次數(shù) ''' # 初始化 print(Counter("aabbbc")) # Counter({'b': 3, 'a': 2, 'c': 1}) print(Counter(['a', 'a', 'b', 'b', 'b', 'c'])) # Counter({'b': 3, 'a': 2, 'c': 1}) print(Counter(a=2, b=3, c=1)) # Counter({'b': 3, 'a': 2, 'c': 1})c = Counter("aaabbc") # 表示a出現(xiàn)了三次,b出現(xiàn)了兩次,c出現(xiàn)了一次 print(c) # Counter({'a': 3, 'b': 2, 'c': 1}) # 可以進(jìn)行填充 c.update("bcd") # 可以看到b和c的值都增加了1,并且出現(xiàn)了d print(c) # Counter({'a': 3, 'b': 3, 'c': 2, 'd': 1})# 訪問計(jì)數(shù),Counter對(duì)象可以像字典一樣訪問 print(c["a"]) # 3 # 如果訪問一個(gè)不存在的key,不會(huì)引發(fā)KeyError,而是會(huì)返回0,表示對(duì)象中沒有這個(gè)key print(c["mmp"]) # 0# 還可以使用elements進(jìn)行迭代,會(huì)得到Counter對(duì)象中的所有元素 print(list(c.elements())) # ['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'd']# 還可以計(jì)算出現(xiàn)最多的元素 # 統(tǒng)計(jì)string中前三個(gè)出現(xiàn)次數(shù)最多的元素 string = "sasaxzsdsadfscxzcasdscxzdfscxsasadszczxczxcsds" c = Counter(string) print(c) # Counter({'s': 13, 'c': 7, 'a': 6, 'x': 6, 'z': 6, 'd': 6, 'f': 2}) print(c.most_common(3)) # [('s', 13), ('c', 7), ('a', 6)]# Counter還可以進(jìn)行算數(shù)操作 c1 = Counter("aabbccc") c2 = Counter("bbbccdd") print(c1) # Counter({'a': 2, 'b': 2, 'c': 3}) print(c2) # Counter({'b': 3, 'c': 2, 'd': 2}) # 如果c1的元素出現(xiàn)在了c2中,就把該元素減去,記住:減的是次數(shù) print(c1 - c2) # Counter({'a': 2, 'c': 1}) ''' a在c1中出現(xiàn)了2次,c2中沒有出現(xiàn),所有是a: 2。b在c1中出現(xiàn)兩次,在c2中出現(xiàn)3次,所以一減就沒有了。 而c在c1中出現(xiàn)了三次,在c2中出現(xiàn)兩次,所以相減還剩下一次。至于c1沒有的元素就不用管了 '''# 相加就很好理解了 print(c1 + c2) # Counter({'b': 5, 'c': 5, 'a': 2, 'd': 2})# 相交的話,查找公共的元素,并且取次數(shù)出現(xiàn)較小的那個(gè) print(c1 & c2) # Counter({'b': 2, 'c': 2})# 并集的話,取較大的,記住不是相加,所以b和c出現(xiàn)的次數(shù)不會(huì)增加,只是取較大的那個(gè)、 print(c1 | c2) # Counter({'b': 3, 'c': 3, 'a': 2, 'd': 2})

  

3.defaultdict:缺少的鍵返回一個(gè)默認(rèn)值

from collections import defaultdict''' 標(biāo)準(zhǔn)字典中有setdefault和get,可以用來獲取key對(duì)應(yīng)的value。 如果key存在,兩者會(huì)獲取key對(duì)應(yīng)的value 但如果key不存在,setdefault就會(huì)先將key和指定的默認(rèn)值設(shè)置進(jìn)去,然后返回一個(gè)默認(rèn)值。 而get則只會(huì)返回默認(rèn)值,。不會(huì)設(shè)置值 example:d = {"a": 1}print(d.get("a", 0)) # 1print(d.setdefault("a", 0)) # 1print(d) # {"a": 1}print(d.get("b", 0)) # 0print(d) # {"a": 1}print(d.setdefault("b", 0)) # 0print(d) # {"a": 1, "b": 0}所以這里相當(dāng)于執(zhí)行了兩步操作。先將("b", 0)設(shè)置到字典里,然后再獲取defaultdict在初始化的時(shí)候就會(huì)讓調(diào)用者提前指定默認(rèn)值 '''s = "aabbccdddddee" d1 = {} for c in s:d1.setdefault(c, 0)d1[c] += 1 print(d1) # {'a': 2, 'b': 2, 'c': 2, 'd': 5, 'e': 2}# 如果使用defaultdict的話呢? d2 = defaultdict(int) print(d2["a"]) # 0 d2 = defaultdict(str) print("%r" % d2["a"]) # '' d2 = defaultdict(tuple) print(d2["a"]) # () d2 = defaultdict(list) print(d2["a"]) # [] # 如果獲取不到key,那么會(huì)自動(dòng)輸出傳入類型所對(duì)應(yīng)的零值.能獲取到key,輸入key對(duì)應(yīng)的value值s = "aabbccdddddee" d2 = defaultdict(int) for c in s:'''一開始沒有值,設(shè)置為0,然后每來一個(gè)值就加上1'''d2[c] += 1 print(d2) # defaultdict(<class 'int'>, {'a': 2, 'b': 2, 'c': 2, 'd': 5, 'e': 2})# 此外還可以自定義,只需要添加一個(gè)不需要參數(shù)的函數(shù)即可,指定一個(gè)返回值 d3 = defaultdict(lambda: "default") print(d3["aa"]) # default # 此外還可以添加參數(shù) d4 = defaultdict(lambda: "default", aa="bar") print(d4["aa"]) # bar# 這種字典是如何實(shí)現(xiàn)的呢?主要是內(nèi)部實(shí)現(xiàn)了一個(gè)__missing__魔法方法 class MyDict(dict):def __init__(self, *args, **kwargs):super().__init__(*args, **kwargs)def __getitem__(self, item):value = super().__getitem__(item)# 會(huì)執(zhí)行父類的__getitem__方法,如果獲取不到# 會(huì)檢測(cè)我們是否定義__missing__方法,如果有,執(zhí)行。沒有,報(bào)錯(cuò)# 所以這里的value就是__missing__方法的返回值return valuedef __missing__(self, key):self[key] = "為什么渣男這么吃香,像我們這樣暖人心的老實(shí)人卻不受待見"return self[key]d = MyDict([("a", 3), ("b", 4)]) print(d) # {'a': 3, 'b': 4} print(d["mmm"]) # 為什么渣男這么吃香,像我們這樣暖人心的老實(shí)人卻不受待見

  

4.deque:雙端隊(duì)列

?

from collections import deque''' 雙端隊(duì)列支持從任意一端增加和刪除元素。更為常用的兩種數(shù)據(jù)結(jié)構(gòu)(即棧和隊(duì)列)就是雙端隊(duì)列的退化形式,它們的輸入和輸出被限制在某一端 ''' d = deque("abcdefg") print(d) # deque(['a', 'b', 'c', 'd', 'e', 'f', 'g']) print(len(d)) # 7 print(d[0]) # a print(d[-1]) # g# 由于deque是一種序列容器,因此同樣支持list的操作。如:通過索引獲取元素,查看長(zhǎng)度,刪除元素,反轉(zhuǎn)元素等等 # list支持的deque基本上都支持 d.reverse() print(d) # deque(['g', 'f', 'e', 'd', 'c', 'b', 'a']) d.remove("c") print(d) # deque(['g', 'f', 'e', 'd', 'b', 'a'])# 填充元素 # 首先可以像list一樣添加元素,但是deque可以從兩端添加 d.append("yoyoyo") # 默認(rèn)和list一樣,在尾部添加 d.appendleft("喲喲喲") # 也可以添加在頭部 print(d) # deque(['喲喲喲', 'g', 'f', 'e', 'd', 'b', 'a', 'yoyoyo']) # 還可以使用insert, 如果范圍越界,自動(dòng)添加在兩端 d.insert(100, "x") print(d) # deque(['喲喲喲', 'g', 'f', 'e', 'd', 'b', 'a', 'yoyoyo', 'x'])# extend,extendleft d1 = [1, 2, 3] d2 = deque([4, 5, 6]) d.extend(d1) print(d) # deque(['喲喲喲', 'g', 'f', 'e', 'd', 'b', 'a', 'yoyoyo', 'x', 1, 2, 3]) d.extendleft(d2) print(d) # deque([6, 5, 4, '喲喲喲', 'g', 'f', 'e', 'd', 'b', 'a', 'yoyoyo', 'x', 1, 2, 3]) # 可以看到extend也支持從左端添加,而且不僅僅可以添加deque,任意序列類型都是可以的。 d.extendleft("我屮艸芔茻") print(d) # deque(['茻', '芔', '艸', '屮', '我', 6, 5, 4, '喲喲喲', 'g', 'f', 'e', 'd', 'b', 'a', 'yoyoyo', 'x', 1, 2, 3]) # 注意添加的順序,我們是從左邊開始添加的,先添加"我",然后"屮"跑到開頭就把"我"擠到右邊了,所以是結(jié)果是倒過來的# 那么如果消費(fèi)deque里面的元素呢? print(d.pop()) # 3 print(d.pop()) # 2 print(d.pop()) # 1 print(d.pop()) # x print(d.popleft()) # 茻 # pop是從右端刪除一個(gè)元素,popleft是從左端開始刪除一個(gè)元素。但是如果我想pop掉指定的索引的元素,只能用pop函數(shù),傳入索引值即可 # 注意:deque和queue一樣,是線程安全的,是受GIL這把超級(jí)大鎖保護(hù)的,可以不同的線程中進(jìn)行消費(fèi)。 # 如果想清空里面的元素的話,可以像list、dict一樣,使用clear函數(shù) d.clear() print(d) # deque([])# 旋轉(zhuǎn) # deque還有一個(gè)很用的地方就是可以按任意一個(gè)方向進(jìn)行旋轉(zhuǎn),從而跳過某些元素。 # d.rotate(n)-->n大于0,從右邊開始取n個(gè)元素放到左邊,n小于0,從左邊取n個(gè)元素放到右邊 d = deque(range(10)) print(d) # deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) d.rotate(2) # 從右邊取2個(gè)元素放到左邊,所以8和9被放到了左邊 print(d) # deque([8, 9, 0, 1, 2, 3, 4, 5, 6, 7]) d.rotate(-3) # 從左邊取3個(gè)元素放到右邊,所以8、9、0被放到了右邊 print(d) # deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 0])# 限制隊(duì)列的大小 # 我們?cè)诔跏蓟粋€(gè)雙端隊(duì)列的時(shí)候,還可以限制它的大小 d = deque("abcdefg", maxlen=5) # 我們初始化7個(gè)元素,但是指定最大長(zhǎng)度只有5,所以前面兩個(gè)("a"和"b")就被擠出去了 print(d) # deque(['c', 'd', 'e', 'f', 'g'], maxlen=5) d.appendleft("yoyoyo") # 當(dāng)我往前面添加元素的時(shí)候,后面的就被擠出去了,因?yàn)殛?duì)列最多只能容納5個(gè)元素 print(d) # deque(['yoyoyo', 'c', 'd', 'e', 'f'], maxlen=5)

?

  

5.namedtuple:帶名字字段的元組子類

from collections import namedtuple# 傳入名字,和字段 person = namedtuple("person", ["name", "age", "gender"]) person1 = person(name="mashiro", age=16, gender="f") print(person1) # person(name='mashiro', age=16, gender='f') print(person1.name, person1.age, person1.gender) # mashiro 16 f print(person1[0]) # mashiro ''' 可以看到不僅可以像普通的tuple一樣使用索引訪問,還可以使用像類一樣通過.字段名訪問 '''person2 = person("satori", 16, "f") print(person2) # person(name='satori', age=16, gender='f')''' 注意:這個(gè)和普通的元組一樣,是不可以修改的 ''' try:person2.name = "xxx" except AttributeError as e:print(e) # can't set attribute# 非法字段名,不能使用Python的關(guān)鍵字 try:girl = namedtuple("女孩們", ["for", "in"]) except ValueError as e:print(e) # Type names and field names cannot be a keyword: 'for'# 如果字段名重復(fù)了怎么辦 try:girl = namedtuple("女孩們", ["name", "age", "age"]) except ValueError as e:print(e) # Encountered duplicate field name: 'age'# 如果非要加上重名字段呢,可以設(shè)置一個(gè)參數(shù) girl = namedtuple("女孩們", ["name", "age", "age"], rename=True) print(girl) # <class '__main__.女孩們'> girl1 = girl("koishi", 15, 15) # 可以看到重復(fù)的字段名會(huì)按照索引的值,在前面加上一個(gè)下劃線。比如第二個(gè)age重復(fù),它的索引是多少呢?是2,所以默認(rèn)幫我們把字段名修改為_2 print(girl1) # 女孩們(name='koishi', age=15, _2=15)# 此外我們所有的字段名都保存在_fields屬性中 print(girl1._fields) # ('name', 'age', '_2')

  

6.OrderDict:記住字典增加鍵的順序

from collections import OrderedDict''' OrderDict是一個(gè)字典子類,可以記住字典中增加鍵的順序。 在Python2中,字典是無序的,但在Python3中,字典默認(rèn)是有序的 ''' d = OrderedDict() d["a"] = "A" d["b"] = "B" d["c"] = "C" for k, v in d.items():print(k, v) ''' a A b B c C ''' # 此外也可以在初始化的時(shí)候,添加元素 print(OrderedDict({"a": 1})) # OrderedDict([('a', 1)])# 相等性,對(duì)于常規(guī)字典來說,只要里面元素一樣便是相等的,不考慮順序。但是對(duì)于OrderDict來說,除了元素,順序也要一樣,否則就不相等 d1 = {"a": 1, "b": 2} d2 = {"b": 2, "a": 1} print(d1 == d2) # Trued1 = OrderedDict({"a": 1, "b": 2}) d2 = OrderedDict({"b": 2, "a": 1}) print(d1 == d2) # False# 重排 # 在OrderDict中可以使用move_to_end()將鍵移至序列的起始位置或末尾位置來改變鍵的順序 d3 = OrderedDict({"a": 1, "b": 2, "c": 3, "d": 4}) d3.move_to_end("c") # 表示將key="c"的這個(gè)鍵值對(duì)移動(dòng)到末尾 print(d3) # OrderedDict([('a', 1), ('b', 2), ('d', 4), ('c', 3)]) d3.move_to_end("c", last=False) # 表示將key="c"的這個(gè)鍵值對(duì)移動(dòng)到行首 print(d3) # OrderedDict([('c', 3), ('a', 1), ('b', 2), ('d', 4)])

  

7.collections.abc:容器的抽象基類

from collections import abc''' abc模塊包含了一些抽象基類,其為Python內(nèi)置容器數(shù)據(jù)結(jié)構(gòu)以及collections模塊定義的容器數(shù)據(jù)結(jié)構(gòu)定義了API。 除了明確地定義不同容器的API,這些抽象基類還可以在調(diào)用對(duì)象之前用instance()測(cè)試一個(gè)對(duì)象是否支持一個(gè)API。 有些類還提供了方法實(shí)現(xiàn),它們可以作為"混入類(mix-in)"構(gòu)造定制容器,而不必從頭實(shí)現(xiàn)每一個(gè)方法。 '''

  

(三)數(shù)組:固定的數(shù)據(jù)序列

import array''' array模塊定義了一個(gè)序列數(shù)據(jù)結(jié)構(gòu),看起來與list很相似,只不過所以成員都必須是相同的數(shù)據(jù)類型。 支持的類型包括數(shù)值類型或其他固定大小的基本類型(比如:字節(jié))array成員的類型代碼 代碼 類型 最小大小(字節(jié)) b Int 1 B INT 1 h Signed short 2 H Unsigned short 2 i Signed int 2 I Unsigned int 2 l Signed long 4 L Unsigned long 4 q Signed long long 8 Q Unsigned long long 8 f Float 4 d Double 8 '''

  

1.初始化

import array import binascii''' array被實(shí)例初始化時(shí)可以提供一個(gè)參數(shù)來描述允許哪種數(shù)據(jù)類型,還可以有一個(gè)存儲(chǔ)在數(shù)組中的初始數(shù)據(jù)序列。 ''' s = b"this is a array" a = array.array("b", s) print("byte string:", s) # byte string: b'this is a array' print("as array:", a) # as array: array('b', [116, 104, 105, 115, 32, 105, 115, 32, 97, 32, 97, 114, 114, 97, 121]) print("as hex:", binascii.hexlify(a)) # as hex: b'746869732069732061206172726179'

  

2.處理數(shù)組

import array''' 與其他Python序列類似,可以采用同樣的方式擴(kuò)展和處理array ''' a = array.array("i", range(3)) print("初始化:", a) # 初始化: array('i', [0, 1, 2]) a.extend(range(3)) print("擴(kuò)展:", a) # 擴(kuò)展: array('i', [0, 1, 2, 0, 1, 2]) print("切片:", a[2: 5]) # 切片: array('i', [2, 0, 1]) print("迭代器:", list(enumerate(a))) # 迭代器: [(0, 0), (1, 1), (2, 2), (3, 0), (4, 1), (5, 2)]

  

3.數(shù)組和文件

import array import binascii import tempfile''' 可以使用專門的高效讀寫文件的內(nèi)置方法將數(shù)組的內(nèi)容寫入文件或從文件讀出數(shù)組 ''' a = array.array("i", range(5)) print("A1:", a) # A1: array('i', [0, 1, 2, 3, 4])# 將數(shù)組的元素寫到臨時(shí)文件當(dāng)中,那么首先要?jiǎng)?chuàng)建一個(gè)臨時(shí)文件 output = tempfile.TemporaryFile() # tofile函數(shù)會(huì)把數(shù)組a里面的內(nèi)容寫到output這個(gè)臨時(shí)文件當(dāng)中 a.tofile(output) # 刷新 output.flush()# 讀取數(shù)據(jù) # 讀取內(nèi)容 output.seek(0) raw_data = output.read() print("raw_content:", binascii.hexlify(raw_data)) # raw_content: b'0000000001000000020000000300000004000000'# 將內(nèi)容讀到數(shù)組當(dāng)中,但是我們剛才讀過一遍了,因此別忘了把指針移到文件的開頭 output.seek(0) # 創(chuàng)建新數(shù)組 a2 = array.array("i") # 調(diào)用fromfile把f里面的內(nèi)容寫到數(shù)組a2里面去,寫入的長(zhǎng)度和數(shù)組a的長(zhǎng)度一樣 a2.fromfile(output, len(a)) print("A2:", a2) # A2: array('i', [0, 1, 2, 3, 4])# 其中tofile使用tobytes格式化數(shù)據(jù),fromfile使用frombytes再轉(zhuǎn)化為一個(gè)數(shù)組實(shí)例a = array.array("i", range(5)) print("a:", a) # a: array('i', [0, 1, 2, 3, 4]) as_bytes = a.tobytes() print("as_bytes:", binascii.hexlify(as_bytes)) # as_bytes: b'0000000001000000020000000300000004000000'a2 = array.array("i") a2.frombytes(as_bytes) print(a2) # array('i', [0, 1, 2, 3, 4])

  

(四)heapq:堆排序算法

import heapq''' 堆(heap)是一個(gè)樹形數(shù)據(jù)結(jié)構(gòu),其中子節(jié)點(diǎn)與父節(jié)點(diǎn)有一種有序關(guān)系。 二叉堆(binary heap)可以使用一個(gè)有組織的列表或數(shù)組表示,其中元素N的子元素位于2*N+1和2*N+2(索引從0開始)。 這種布局允許原地重新組織對(duì),從而不必在增加或刪除元素時(shí)重新分配大量?jī)?nèi)存。 最大堆(max-heap)確保父節(jié)點(diǎn)大于或等于其兩個(gè)子節(jié)點(diǎn),最小堆(min-heap)要求父節(jié)點(diǎn)小于或等于其子節(jié)點(diǎn)。Python的heapq實(shí)現(xiàn)了一個(gè)最小堆 '''

  

import heapq''' 創(chuàng)建堆有兩種方式:heappush()和heapify() ''' ''' 最主要我們可以使用heapq來獲取序列極值 ''' data = [1, 1, 2, 2, 2, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 6, 6] # 從data中選出3個(gè)最大的元素 print(heapq.nlargest(3, data)) # [6, 6, 5] # 從data中選出3個(gè)最大的元素,按照什么來選,出現(xiàn)的次數(shù)來選,也就是選擇出現(xiàn)次數(shù)最多的三個(gè)元素,這里顯然都是4 print(heapq.nlargest(3, data, key=lambda x: data.count(x))) # [4, 4, 4]data = {"a": 3, "b": 2, "c": 11, "d": 1, "e": 16} # 選擇三個(gè)最大的元素,按照value來選擇 print(heapq.nlargest(3, data, lambda x: data[x])) # ['e', 'c', 'a']# 高效合并有序序列 data1 = "abc" data2 = [1, 2, 3] data3 = {4, 5, 6} data4 = {"k": 1, "m": 2} from itertools import chain data = chain(data1, data2, data3, data4) # 得到的data是一個(gè)迭代器 print(list(data)) # ['a', 'b', 'c', 1, 2, 3, 4, 5, 6, 'k', 'm']

  

(五)bisect:維護(hù)有序的列表

import bisect''' bisect模塊實(shí)現(xiàn)了一個(gè)算法來向列表中插入元素,同時(shí)仍然保證列表有序 '''

?

  

1.有序插入

import bisect''' 可以使用bisect.insort向一個(gè)列表中插入元素 ''' values = [46, 39, 80, 91, 62, 71, 32, 70, 15, 6] # 比如我此刻往values里面插入50這個(gè)元素 bisect.insort(values, 50) print(values) # [46, 39, 50, 80, 91, 62, 71, 32, 70, 15, 6] ''' bisect默認(rèn)是從小到大的,因此46比50小,39比50小,80比50大,因此插在了39和80之間 至于我們的values本身就是無序的,因此不可能會(huì)把values變成有序的。只能找一個(gè)左邊比要插入的值小,右邊比要插入的值大,然后在這個(gè)位置把值插進(jìn)去 ''' # 我們新建一個(gè)列表 l = [] for i in values:bisect.insort(l, i)print("-"*20)print(f"{i} {l}")'''--------------------46 [46]--------------------39 [39, 46]--------------------50 [39, 46, 50]--------------------80 [39, 46, 50, 80]--------------------91 [39, 46, 50, 80, 91]--------------------62 [39, 46, 50, 62, 80, 91]--------------------71 [39, 46, 50, 62, 71, 80, 91]--------------------32 [32, 39, 46, 50, 62, 71, 80, 91]--------------------70 [32, 39, 46, 50, 62, 70, 71, 80, 91]--------------------15 [15, 32, 39, 46, 50, 62, 70, 71, 80, 91]--------------------6 [6, 15, 32, 39, 46, 50, 62, 70, 71, 80, 91]''' ''' 這是一個(gè)很簡(jiǎn)單的例子,實(shí)際上,對(duì)于這種數(shù)據(jù)量來說,直接構(gòu)建列表完成完成一次排序可能速度會(huì)更快。 不過對(duì)于長(zhǎng)列表而言,使用類似這樣一個(gè)插入排序算法可以大大節(jié)省時(shí)間和內(nèi)存。 所以bisect維護(hù)的是一個(gè)有序的序列,如果一個(gè)序列已經(jīng)有序了,我們使用這種方法插入值,比直接插入值再sort的效率更高 '''

  

2.處理重復(fù)

import bisect''' 如果插入一個(gè)序列中已經(jīng)出現(xiàn)的值,該怎么辦呢?是插在左邊呢,還是插在右邊呢? bisect提供了兩種做法,bisect.insort_right和bisect.insort_left,從名字也能看出來。 至于bisect.insort默認(rèn)等于bisect.insort_right,是插在右邊的。這兩種方法沒有什么區(qū)別 '''# 不過bisect還有一種用法 values = [1, 3, 5, 7, 11] # 首先我想插入一個(gè)元素7,可以使用bisect.insort,但是如果我想獲取7這個(gè)元素該插在哪個(gè)位置,該怎么辦呢? # 可以使用bisect.bisect print(bisect.bisect(values, 7)) # 4 ''' 結(jié)果為4,表示我應(yīng)該插在索引為4的這個(gè)地方,原來索引為4的地方是11 所以當(dāng)7插進(jìn)去之后,那么11被擠到了后面,因此正好保證有序 同理還有bisect.bisect_left ''' print(bisect.bisect_left(values, 7)) # 3 ''' 得到的結(jié)果為3,至于原因,我想不需要再贅述了 bisect.bisect也等價(jià)于bisect.bisect_right '''

  

(六)queue:線程安全的FIFO(先進(jìn)先出)實(shí)現(xiàn)

import queue ''' queue提供了適用于多線程編程的先進(jìn)先出(FIFO, first-in, first-out)數(shù)據(jù)結(jié)構(gòu),可以用來在生產(chǎn)者和消費(fèi)者線程之間安全地傳遞消息或其他數(shù)據(jù) 它會(huì)為調(diào)用者處理鎖定,使多個(gè)線程能夠安全且容易地處理同一個(gè)Queue實(shí)例。因?yàn)檫@也是受GIL全局鎖保護(hù)的 Queue的大小(可以容納的元素個(gè)數(shù))可能首先,以限制內(nèi)存使用或處理 '''

  

1.基本的FIFO隊(duì)列

import queue''' Queue類實(shí)現(xiàn)了一個(gè)基本的先進(jìn)先出的容器。使用put將元素增加到這個(gè)序列的一端,使用get從另一端獲取。想象一個(gè)管子,兩邊都有口,我從一端塞進(jìn)去,然后從另一端獲取,先塞進(jìn)去的會(huì)先跑到另一端比如我從左邊塞進(jìn)去1 2 3 4這個(gè)四個(gè)元素,1先塞進(jìn)去的話,那么1肯定在最右邊,那么我從另一邊最先獲取的也是1 -------------4 3 2 1------------- 但如果是棧的話,由于只有一個(gè)口,右邊被堵死了,所以即便我從左邊塞進(jìn)去,我還是要從左邊獲取。因此最先放進(jìn)去的,要最后才能出來 -------------·| 4 3 2 1 | | -------------· ''' # 調(diào)用Queue這個(gè)類實(shí)例化一個(gè)隊(duì)列 q = queue.Queue() for i in range(5):# 調(diào)用put方法塞進(jìn)去q.put(i) # 可以查看屬性 # 這里顯示為0是因?yàn)槲覀儧]有指定最大容量 print(q.maxsize) # 0 # 我們沒有指定容量,所以默認(rèn)無限大,只要你內(nèi)存足夠大。 print(q.full()) # False # 判斷隊(duì)列是否為空 print(q.empty()) # False # 查看隊(duì)列的長(zhǎng)度,也就是看看隊(duì)列的肚子里面壞了幾個(gè)孩子(裝了幾個(gè)元素) # 注意不可以使用len(q)來判斷,因?yàn)镼ueue這個(gè)類沒有實(shí)現(xiàn)__len__方法 print(q.qsize()) # 5while not q.empty():# 可以看到實(shí)現(xiàn)了先進(jìn)先出,這與我們插進(jìn)去的順序是一樣的print(q.get(), end=" ") # 0 1 2 3 4# 另外解釋一下這個(gè)get里面的參數(shù) ''' def get(self, block=True, timeout=None): 這個(gè)block,表示是否阻塞,默認(rèn)為True,表示如果獲取不到元素,就會(huì)一直卡在這個(gè)地方,直到隊(duì)列里面有數(shù)據(jù)為止。 如果為False,表示不阻塞,隊(duì)列為空獲取不到元素的時(shí)候會(huì)立即報(bào)錯(cuò),這個(gè)一般是在多線程當(dāng)中會(huì)使用timeout表示超時(shí)時(shí)間,這個(gè)參數(shù)有什么用呢? 比如消費(fèi)者消費(fèi)數(shù)據(jù),但是生產(chǎn)者還沒生產(chǎn)完,所以消費(fèi)者會(huì)卡住,需要生產(chǎn)者生產(chǎn)完數(shù)據(jù)放到隊(duì)列里面去,消費(fèi)者才能獲取數(shù)據(jù)繼續(xù)往下走 如果生產(chǎn)者不生產(chǎn)了,消費(fèi)者卡住。設(shè)置block=False的話呢,可以避免,但是有可能生產(chǎn)者生產(chǎn)的比較慢,不是說不生產(chǎn)了,這種情況會(huì)導(dǎo)致生產(chǎn)者還沒把數(shù)據(jù)放進(jìn)去的時(shí)候就已經(jīng)報(bào)錯(cuò)了 所以timeout的作用就來了,表示消費(fèi)者獲取不到數(shù)據(jù)的時(shí)候,依舊會(huì)卡住,但是不會(huì)一直卡住,會(huì)保持一段時(shí)間,如果時(shí)間過后還沒有數(shù)據(jù)的話再報(bào)錯(cuò)。 所以要搭配block=True來使用,如果block=False的話,這個(gè)參數(shù)就沒有什么意義了,因?yàn)楂@取不到直接報(bào)錯(cuò),就沒有timeout什么事了。
同理對(duì)于put也是一樣,如果數(shù)據(jù)滿了,放不進(jìn)去了,put也提供了block和timeout參數(shù)供我們使用 '''

  

2.LIFO隊(duì)列

?

import queue''' 與Queue的標(biāo)準(zhǔn)FIFO實(shí)現(xiàn)相反,LifoQueue使用了(通常與棧數(shù)據(jù)結(jié)構(gòu)關(guān)聯(lián)的)后進(jìn)先出(LIFO)順序 ''' q = queue.LifoQueue() for i in range(5):q.put(i)while not q.empty():print(q.get(), end=" ") # 4 3 2 1 0 # 可以看到,這個(gè)和棧比較類似,是先入后出的

  

3.優(yōu)先隊(duì)列

import queue import functools import threading''' 有些情況下,需要根據(jù)隊(duì)列中元素的特性來決定這些元素的處理順序,而不是簡(jiǎn)單地采用隊(duì)列中創(chuàng)建或插入元素的順序。 例如,工資部門的打印作業(yè)可能就優(yōu)先于某個(gè)開發(fā)人員要想打印的代碼清單。PriorityQueue使用隊(duì)列內(nèi)容的有序順序來決定獲取哪一個(gè)元素 '''@functools.total_ordering class Job:def __init__(self, priority, description):self.priority = priorityself.description = descriptionprint("New Job:", description)def __eq__(self, other):try:return self.priority == other.priorityexcept AttributeError:return NotImplementeddef __lt__(self, other):try:return self.priority < other.priorityexcept AttributeError:return NotImplementedq = queue.PriorityQueue() q.put(Job(3, "優(yōu)先級(jí)第3")) q.put(Job(10, "優(yōu)先級(jí)第10")) q.put(Job(1, "優(yōu)先級(jí)第1"))def process_job(q):while q.qsize() > 0:next_job = q.get()print("processing job", next_job.description)q.task_done()process_job(q) workers = [threading.Thread(target=process_job, args=(q, )),threading.Thread(target=process_job, args=(q, )) ] for w in workers:w.start()''' 這個(gè)的q.join()和上面的q.task_done()有必要說一下。 首先隊(duì)列中有這么一個(gè)屬性,在Queue這個(gè)類的構(gòu)造函數(shù)中:self.unfinished_tasks = 0 這個(gè)表示什么含義呢,表示未完成的任務(wù),也就是說我們每put一次,這個(gè)計(jì)數(shù)就會(huì)加上1def put(self, item, block=True, timeout=None):省略。。。self._put(item)self.unfinished_tasks += 1self.not_empty.notify() 使用join的話,是等這個(gè)計(jì)數(shù)變?yōu)?才能往下走,否則就會(huì)卡住。 因此在我們?nèi)〕鲆粋€(gè)元素的時(shí)候,要記得調(diào)用一下q.task_done()來讓計(jì)數(shù)減1def task_done(self):with self.all_tasks_done:unfinished = self.unfinished_tasks - 1if unfinished <= 0: ''' q.join() # 輸出結(jié)果 ''' New Job: 優(yōu)先級(jí)第3 New Job: 優(yōu)先級(jí)第10 New Job: 優(yōu)先級(jí)第1 processing job 優(yōu)先級(jí)第1 processing job 優(yōu)先級(jí)第3 processing job 優(yōu)先級(jí)第10 '''
# 可以看到會(huì)順序打印,為什么?那是因?yàn)閮?yōu)先級(jí)隊(duì)列會(huì)自動(dòng)排序,而我們定義好了排序規(guī)則''' 這個(gè)例子有多個(gè)線程在處理作業(yè),要根據(jù)get()時(shí)隊(duì)列中元素的優(yōu)先級(jí)來處理。 運(yùn)行消費(fèi)者線程時(shí),增加到隊(duì)列的元素的處理順序取決于線程上下文切換。 '''

  

4.構(gòu)建一個(gè)多線程播客客戶端程序

?

from queue import Queue''' 現(xiàn)在我們將構(gòu)建一個(gè)播客客戶端程序,程序的代碼展示了如何利用多個(gè)線程使用Queue類。 這個(gè)程序要讀入一個(gè)或多個(gè)rss提要,對(duì)每一個(gè)提要的專輯排隊(duì),顯示最新的五集以供下載,并使用多線程并發(fā)處理多個(gè)下載。 這里沒有提供完備的錯(cuò)誤處理,所以不能在實(shí)際的生產(chǎn)環(huán)境中使用,不過這個(gè)框架可以作為很好的例子來說明如何使用queue模塊。首先要建立一些操作參數(shù),一般情況下,這些參數(shù)都來自用戶輸入(例如,首選項(xiàng),數(shù)據(jù)庫等)。 不過在這個(gè)例子中,線程數(shù)和要獲取的url列表都采用了硬編碼值 ''' import threading import requests from urllib.parse import urlparseimport feedparser # 用來解析rss文本的一個(gè)模塊# 設(shè)置一些全局變量 num_fetch_threads = 2 # 線程數(shù) enclosure_queue = Queue() feed_urls = ["https://talkpython.fm/episodes/rss" ]def message(s):print(f"{threading.current_thread().name}: {s}")# 函數(shù)download_enclosure在工作線程中運(yùn)行,使用urllib處理下載 def download_enclosures(q):'''這是一個(gè)工作線程函數(shù),一個(gè)接一個(gè)地處理每一個(gè)成員。而這些守護(hù)線程會(huì)進(jìn)入一個(gè)無限循環(huán),只有當(dāng)主線程結(jié)束時(shí)才會(huì)推出:param q::return:'''while True:message("尋找下一個(gè)閉包")url = q.get()# 獲取url最后一個(gè)/右面的內(nèi)容當(dāng)做文件名filename = url.rpartition("/")[-1]# 下載文件message(f"downloading {filename}")# 下載內(nèi)容response = requests.get(url)data = response.content# 保存內(nèi)容到filename當(dāng)中with open(filename, "wb") as f:f.write(data)# 不要忘記處理完了之后,要task_doneq.task_done()''' 一旦定義了線程的目標(biāo)函數(shù),接下來便可以啟動(dòng)工作線程。 download_enclosures處理語句url=q.get()時(shí),會(huì)阻塞并等待,直到隊(duì)列返回某個(gè)結(jié)果。 這說明,即使隊(duì)列中沒有任何內(nèi)容,也可以安全地啟動(dòng)線程 '''# 啟動(dòng)一些線程 for i in range(num_fetch_threads):worker = threading.Thread(target=download_enclosures,args=(enclosure_queue,),name=f"worker-{i}")worker.setDaemon(True)worker.start()''' 下一步使用feedparser模塊獲取提要內(nèi)容,并將這些專輯的url入隊(duì)。 一旦第一個(gè)url增加到隊(duì)列,就會(huì)有某個(gè)工作線程提取這個(gè)url,并且還是下載。 這個(gè)循環(huán)會(huì)繼續(xù)增加元素,直到這個(gè)提要已經(jīng)被完全消費(fèi),工作線程會(huì)依次將url出隊(duì)以完成下載 '''for url in feed_urls:response = feedparser.parse(url)for entry in response["entries"][: 5]:for enclosure in entry.get("enclosures", []):parsed_url = urlparse(enclosure["url"])message(f"queuing {parsed_url.path.rpartition('/')[-1]}")enclosure_queue.put(enclosure['url'])# 現(xiàn)在我們要使用join等待隊(duì)列為空 message("***main thread waiting") enclosure_queue.join() message("***done") # 輸出內(nèi)容不再顯示

  

(七)struct:二進(jìn)制數(shù)據(jù)結(jié)構(gòu)

import struct''' struct模塊包括一些函數(shù),這些函數(shù)可以完成字節(jié)串與原生Python數(shù)據(jù)類型(如數(shù)字和字符串)之間的轉(zhuǎn)換 '''

  

1.函數(shù)與struct類

import struct''' struct提供了一組處理結(jié)構(gòu)值的模塊級(jí)函數(shù),另外還有一個(gè)Struct類。 格式指示符有字符串格式轉(zhuǎn)換為一種編譯表示,這與處理正則表達(dá)式的方法類似。 這個(gè)轉(zhuǎn)換會(huì)耗費(fèi)一些資源,所以創(chuàng)建一個(gè)Struct實(shí)例并在這個(gè)實(shí)例上調(diào)用方法時(shí)(不使用模塊級(jí)函數(shù))只完成一次轉(zhuǎn)換,這會(huì)更高效類比正則: re.match(pattern, text) 使用這種模塊級(jí)別的函數(shù)時(shí),會(huì)先將pattern進(jìn)行編譯轉(zhuǎn)換,這個(gè)轉(zhuǎn)換是耗費(fèi)資源的因此可以先對(duì)pattern進(jìn)行一個(gè)編譯,comp = re.compile(pattern) comp.match(text) 這樣的話就只需要轉(zhuǎn)換一次,struct也是類似的情況 '''

  

2.打包與解包

import struct''' Struct支持使用格式指示符將數(shù)據(jù)打包(packing)為字符串,另外支持從字符串解包(unpacking)數(shù)據(jù)。 格式指示符由表示數(shù)據(jù)類型的字符和可選的數(shù)量及字節(jié)序(endianness)指示符構(gòu)成。 要全面了解目前可支持的數(shù)據(jù)結(jié)構(gòu),可以參考標(biāo)準(zhǔn)庫文檔 ''' import binascii# values包含一個(gè)整型或長(zhǎng)整型,一個(gè)兩字節(jié)字符串,以及一個(gè)浮點(diǎn)數(shù)。 values = (1, "ab".encode("utf-8"), 2.7) # 格式指示符中包含的空格用來分割類型指示符,并且在編譯格式時(shí)會(huì)被忽略 # 使用Struct定義格式,I:整型,2s:兩個(gè)字節(jié)的字符,f:浮點(diǎn)數(shù),之間使用空格分隔 # 表示打包的數(shù)據(jù)有三個(gè),分別是整型,兩個(gè)字節(jié)的字符,以及一個(gè)浮點(diǎn) s = struct.Struct("I 2s f") # 使用s.pack函數(shù)進(jìn)行打包,將values打開傳進(jìn)去 packed_data = s.pack(*values)# s:Struct對(duì)象 print(s) # <Struct object at 0x0000000002924458> # 原始數(shù)據(jù)values print("原始數(shù)據(jù):", values) # 原始數(shù)據(jù): (1, b'ab', 2.7) # 打印一下我們的格式,也就是我們傳進(jìn)去的格式 print("格式化字符:", s.format) # 格式化字符: I 2s f # 查看所用的字節(jié) print("使用:", s.size, "bytes") # 使用: 12 bytes # 查看打包之后的結(jié)果 print("打包后的結(jié)果:", packed_data) # 打包后的結(jié)果: b'\x01\x00\x00\x00ab\x00\x00\xcd\xcc,@' print("將打包的結(jié)果進(jìn)行轉(zhuǎn)換:", binascii.hexlify(packed_data)) # 將打包的結(jié)果進(jìn)行轉(zhuǎn)換: b'0100000061620000cdcc2c40'# 我們傳入values,通過s.pack()得到packed_data,那么我們傳入packed_data,可不可以調(diào)用一個(gè)函數(shù)反過來得到values呢? # 答案是可以的,可以使用s.unpack() # 值得一提的是,這個(gè)binascii.hexlify,還有一個(gè)相反的函數(shù)叫做binascii.unhexlify print(packed_data) # b'\x01\x00\x00\x00ab\x00\x00\xcd\xcc,@' print(binascii.hexlify(packed_data)) # b'0100000061620000cdcc2c40' print(binascii.unhexlify(binascii.hexlify(packed_data))) # b'\x01\x00\x00\x00ab\x00\x00\xcd\xcc,@'# 使用s.unpack() print(s.unpack(packed_data)) # (1, b'ab', 2.700000047683716) ''' 可以看到還是可以轉(zhuǎn)回來的,注意這個(gè)浮點(diǎn)數(shù)啊,這是計(jì)算機(jī)的存儲(chǔ)誤差,任何語言都是有這個(gè)問題的。 '''

  

3.字節(jié)序

import struct''' 默認(rèn)地,值會(huì)使用原生C庫的字節(jié)序(endianness)來編碼。 只需在格式中提供一個(gè)顯示的字節(jié)序指令,就可以很容易地覆蓋這個(gè)默認(rèn)選擇 ''' import binasciivalues = (1, "ab".encode("utf-8"), 2.7) print("original values:", values)endianness = [("@", "native, native"),("=", "native, standard"),("<", "little-endian"),(">", "big-endian"),("!", "network") ]for code, name in endianness:s = struct.Struct(code + " I 2s f")packed_data = s.pack(*values)print("*"*20)print("Format string: ", s.format, "for", name)print("uses: ", s.size, "bytes")print("hex packed data:", binascii.hexlify(packed_data))print("unpacked data", s.unpack(packed_data))# @:原生順序 # =:原生標(biāo)準(zhǔn) # <:小端 # >:大端 # !:網(wǎng)絡(luò)順序''' original values: (1, b'ab', 2.7) ******************** Format string: @ I 2s f for native, native uses: 12 bytes hex packed data: b'0100000061620000cdcc2c40' unpacked data (1, b'ab', 2.700000047683716) ******************** Format string: = I 2s f for native, standard uses: 10 bytes hex packed data: b'010000006162cdcc2c40' unpacked data (1, b'ab', 2.700000047683716) ******************** Format string: < I 2s f for little-endian uses: 10 bytes hex packed data: b'010000006162cdcc2c40' unpacked data (1, b'ab', 2.700000047683716) ******************** Format string: > I 2s f for big-endian uses: 10 bytes hex packed data: b'000000016162402ccccd' unpacked data (1, b'ab', 2.700000047683716) ******************** Format string: ! I 2s f for network uses: 10 bytes hex packed data: b'000000016162402ccccd' unpacked data (1, b'ab', 2.700000047683716) '''

  

4.緩沖區(qū)

import struct''' 通常在強(qiáng)調(diào)性能的情況下,或者向擴(kuò)展模塊傳入、傳出數(shù)據(jù)時(shí),才會(huì)處理二進(jìn)制打包數(shù)據(jù)。 通過避免為每個(gè)打包結(jié)構(gòu)分配一個(gè)新緩沖區(qū)所帶來的開銷,這些情況可以得到優(yōu)化。 pack_into和unpack_from方法支持直接寫入預(yù)分配的緩沖區(qū) ''' import binascii import ctypes import arrays = struct.Struct("I 2s f") values = (1, "ab".encode("utf-8"), 2.7) print("original:", values) print("---------------") print("ctypes string buffer")# 創(chuàng)建一個(gè)string緩存,大小為s.size b = ctypes.create_string_buffer(s.size) print("before:", b.raw, binascii.hexlify(b.raw))# s.pack表示打包,s.pack_into表示打包到什么地方,至于第二個(gè)參數(shù)0表示偏移量,表示從頭開始 s.pack_into(b, 0, *values) print("after:", b.raw, binascii.hexlify(b.raw))# s.unpack表示解包,s.unpack_from表示從什么地方解包,參數(shù)0表示偏移量,表示從頭開始 print("unpacked:", s.unpack_from(b, 0))print("---------------") print("array")a = array.array("b", b"\0"*s.size) print("before:", a, binascii.hexlify(a)) s.pack_into(a, 0, *values) print("after:", binascii.hexlify(a)) print("unpacked:", s.unpack_from(a, 0))''' original: (1, b'ab', 2.7) --------------- ctypes string buffer before: b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' b'000000000000000000000000' after: b'\x01\x00\x00\x00ab\x00\x00\xcd\xcc,@' b'0100000061620000cdcc2c40' unpacked: (1, b'ab', 2.700000047683716) --------------- array before: array('b', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) b'000000000000000000000000' after: b'0100000061620000cdcc2c40' unpacked: (1, b'ab', 2.700000047683716) '''

  

(八)weakref:對(duì)象的弱引用

?

import weakref''' weakref支持對(duì)象的弱引用,正常的引用會(huì)增加對(duì)象的引用計(jì)數(shù),并避免它被垃圾回收。 但結(jié)果并不是總和期望的那樣,比如有時(shí)候可能會(huì)出現(xiàn)一個(gè)循環(huán)引用,或者有時(shí)候需要內(nèi)存時(shí)可能要?jiǎng)h除對(duì)象的緩存。 弱引用(weak reference)是一個(gè)不能避免對(duì)象被自動(dòng)清理的對(duì)象句柄 '''

  

1.引用

import weakref''' 對(duì)象的弱引用要通過ref類來管理。要獲取原對(duì)象,可以調(diào)用引用對(duì)象 '''class RefObject:def __del__(self):print("del executed")obj = RefObject() # 創(chuàng)建弱引用 r = weakref.ref(obj)print("obj:", obj) # obj: <__main__.RefObject object at 0x0000000002964470> # 顯示關(guān)聯(lián)RefObject print("ref:", r) # ref: <weakref at 0x000000000051BA48; to 'RefObject' at 0x0000000002964470> # 引用r加上(),等價(jià)于obj,因此得到RefObject的實(shí)例對(duì)象 print("ref()", r()) # ref() <__main__.RefObject object at 0x0000000002964470>print("deleting obj") # deleting obj # 刪除obj執(zhí)行析構(gòu)函數(shù) del obj # del executed # 之前說過調(diào)用r()等價(jià)于調(diào)用obj,但是obj被刪除了,所以返回None print("r():", r()) # r(): None

  

2.引用回調(diào)

import weakref''' ref構(gòu)造函數(shù)可以接受一個(gè)可選的回調(diào)函數(shù),刪除引用所指向的對(duì)象時(shí)就會(huì)調(diào)用這個(gè)回調(diào)函數(shù) '''class RefObject:def __del__(self):print("del executed")def callback(reference):print(f"callback : {reference}")obj = RefObject() r = weakref.ref(obj, callback) ''' 當(dāng)引用已經(jīng)"死亡"而且不再引用原對(duì)象時(shí),這個(gè)回調(diào)會(huì)接受這個(gè)引用對(duì)象作為參數(shù)。 這種特性的一種用法就是從緩存中刪除弱引用對(duì)象。 '''print("obj:", obj) print("ref:", r) print("ref()", r())print("deleting obj") del obj print("r():", r()) ''' obj: <__main__.RefObject object at 0x0000000002964630> ref: <weakref at 0x0000000001D2BA48; to 'RefObject' at 0x0000000002964630> ref() <__main__.RefObject object at 0x0000000002964630> deleting obj del executed callback : <weakref at 0x0000000001D2BA48; dead> # 刪除obj,執(zhí)行回調(diào),顯示dead r(): None '''

  

3.最終化對(duì)象

import weakref''' 清理若對(duì)象引用時(shí)要對(duì)資源完成更健壯的管理,可以使用finalize將回調(diào)與對(duì)象關(guān)聯(lián)。 finalize實(shí)例會(huì)一直保留(直到所關(guān)聯(lián)的對(duì)象被刪除),即使沒有保留最終化對(duì)象的引用 '''class RefObj:def __del__(self):print("xxx")def on_finalize(*args):print(f"on_finalize: {args}")obj = RefObj() weakref.finalize(obj, on_finalize, "callback的參數(shù)")del obj ''' xxx on_finalize: ('callback的參數(shù)',) ''' # finalize的參數(shù)包括要跟蹤的對(duì)象,對(duì)象被垃圾回收時(shí)要調(diào)用的callback,以及參數(shù)(可以是位置參數(shù),也可以是關(guān)鍵字參數(shù))# finalize實(shí)例對(duì)象還有一個(gè)atexit屬性,用來控制程序退出時(shí)是否調(diào)用這個(gè)回調(diào)(如果還未調(diào)用) obj1 = RefObj() f = weakref.finalize(obj1, on_finalize, "callback的參數(shù)") # 默認(rèn)是調(diào)用回調(diào),但是將atexit設(shè)置為False會(huì)禁用這種行為 f.atexit = False ''' 不會(huì)有任何的輸出,注意:這里我沒有顯示的刪除obj1. 因?yàn)樵趂.atexit=True的情況下,即使不刪除也依舊會(huì)執(zhí)行callback,執(zhí)行完callback會(huì)觸發(fā)析構(gòu)函數(shù) '''

  

import weakref''' 如果向finalize實(shí)例提供一個(gè)跟蹤對(duì)象的引用,這便會(huì)導(dǎo)致一個(gè)引用被保留,所以這個(gè)對(duì)象永遠(yuǎn)不會(huì)被垃圾回收 '''class RefObj:def __del__(self):print("xxx")def on_finalize(*args):print(f"on_finalize: {args}")obj = RefObj() obj_id = id(obj) f = weakref.finalize(obj, on_finalize, obj) f.atexit = Falsedel obj import gc for o in gc.get_objects():if id(o) == obj_id:print("found uncollected object in gc") # found uncollected object in gc

  

4.代理

?

import weakref''' 有時(shí)候使用代理比使用弱引用更方便。使用代理可以像使用原對(duì)象一樣,而且不要求在訪問對(duì)象之前先調(diào)用代理。 這說明,可以將代理傳遞到一個(gè)庫,而這個(gè)庫并不知道它接收的是一個(gè)引用而不是真正的對(duì)象。 '''class RefObj:def __init__(self, name):self.name = namedef __del__(self):print("xxx")obj = RefObj("my obj") r = weakref.ref(obj) p = weakref.proxy(obj)print("via obj:", obj.name) # via obj: my obj print("via ref:", r().name) # via ref: my obj print("via proxy:", p.name) # via proxy: my objdel obj # xxx try:# 刪除對(duì)象之后,再調(diào)用引用,打印為Noneprint(r()) # None# 但是如果調(diào)用代理的話,則會(huì)拋出一個(gè)ReferenceErrorprint(p) except Exception as e:print(e) # weakly-referenced object no longer exists

  

(九)copy:復(fù)制對(duì)象

import copy ''' copy模塊包括兩個(gè)函數(shù)copy()和deepcopy(),用于復(fù)制現(xiàn)有的對(duì)象 '''

  

1.淺拷貝

import copy''' copy()創(chuàng)建的淺副本(shallow copy)是一個(gè)新容器,其中填充了原對(duì)象內(nèi)容的引用。 ''' l1 = [1] l2 = copy.copy(l1) print(l1[0] is l2[0]) # True ''' 可以看到只是拷貝了引用 '''l2[0] = 111 print(l1) # [1] print(l2) # [111] ''' 當(dāng)我們對(duì)l2進(jìn)行修改的時(shí)候,由于是重新賦值,所以只會(huì)改變l2,而不會(huì)改變l1 '''# 但是,此時(shí)l1內(nèi)部存放一個(gè)列表,列表是可變的對(duì)象,可以在本地修改或添加內(nèi)容 l1 = [[]] l2 = copy.copy(l1) l2[0].append("xxx") print(l1) # [['xxx']] print(l2) # [['xxx']] ''' 可以看到由于只是拷貝了引用(或者說是地址,指針),因此兩者指向同一片內(nèi)存。 如果是賦值,那么會(huì)指向別處。但是列表是可變類型,是支持在原處修改的,因此修改任意一個(gè)都會(huì)影響另一個(gè) '''# 對(duì)于列表來說,l2 = l1[:],與淺拷貝是等價(jià)的 l1 = [[]] l2 = l1[:] l1[0].append(0) print(l1) # [[0]] print(l2) # [[0]]

  

2.深拷貝

import copy''' 淺拷貝則是是拷貝引用,而深拷貝則是拷貝內(nèi)存里存儲(chǔ)的值 '''class A:def __init__(self, li):self.li = []a = A([]) a1 = copy.copy(a) a1.li.append("xxx") print(a.li) # ['xxx']# 深拷貝 a2 = copy.deepcopy(a) a2.li.remove("xxx") print(a.li) # ['xxx'] ''' 可以看到我對(duì)a2里的li,執(zhí)行remove操作并沒有影響a。 '''l1 = [[]] l2 = l1[:] l2[0].append("111") print(l1) # [['111']] l3 = copy.deepcopy(l1) l3[0].append("222") print(l1) # [['111']] ''' 可以看到對(duì)l3進(jìn)行操作,不會(huì)影響l1。因?yàn)樯羁截愂强截愔?#xff0c;所以l3操作的是自己的內(nèi)存,不會(huì)影響l1 ''' print(id(l1[0])) # 31458440 print(id(l2[0])) # 31458440 print(id(l3[0])) # 31503688 ''' 所以l3[0]的引用也發(fā)生變化了,因?yàn)橐貌辉僦赶蛟瓉淼膬?nèi)存,而是指向新的內(nèi)存 '''

  

(十)pprint:美觀打印數(shù)據(jù)結(jié)構(gòu)

from pprint import pprint''' pprint模塊包含一個(gè)美觀打印機(jī),用于生成數(shù)據(jù)結(jié)構(gòu)的一個(gè)美觀的視圖。格式化工具會(huì)生成數(shù)據(jù)結(jié)構(gòu)的一些表示, 不僅能夠由解釋器正確的解析,還便于人閱讀。輸出會(huì)盡可能放在一行上,分解為多行時(shí)會(huì)縮進(jìn) '''

  

1.打印

from pprint import pprint from string import ascii_letters import random# 生成一堆隨機(jī)數(shù)據(jù) d = {random.randint(1, 9): random.sample(list(ascii_letters), 3) for i in range(1, 20)} # 調(diào)用pprint函數(shù),美觀打印。可以添加一個(gè)stream參數(shù),默認(rèn)是打印到sys.stdout,也就是控制臺(tái) pprint(d) ''' {1: ['O', 'W', 'F'],2: ['s', 'B', 'I'],3: ['Z', 'O', 'z'],4: ['g', 'o', 'w'],5: ['V', 'D', 'b'],6: ['l', 'z', 'q'],7: ['c', 'f', 'C'],8: ['l', 'E', 'u'],9: ['D', 'e', 'C']} ''' # 相比打印在一行,這樣是不是很美觀呢?

  

2.格式化

from pprint import pformat from string import ascii_letters import random''' 如果我們想美觀地把數(shù)據(jù)寫到某一個(gè)地方中,該怎么辦呢? 我們使用pprint可以美觀地打印到控制臺(tái)當(dāng)中,但我如果不想打印,而是存到某一個(gè)地方,或者賦值給某個(gè)變量呢? 可以使用pformat,美觀格式化 '''# 生成一堆隨機(jī)數(shù)據(jù) d = {random.randint(1, 9): random.sample(list(ascii_letters), 3) for i in range(1, 20)}p = pformat(d) print(p) ''' {1: ['g', 'R', 'a'],2: ['O', 'k', 'M'],3: ['y', 'u', 'I'],4: ['V', 'E', 'P'],5: ['c', 'e', 'h'],6: ['B', 'i', 'U'],7: ['w', 'z', 'K'],8: ['x', 'z', 'q'],9: ['I', 'B', 'W']} '''

  

?

轉(zhuǎn)載于:https://www.cnblogs.com/traditional/p/10444299.html

總結(jié)

以上是生活随笔為你收集整理的3.Python3标准库--数据结构的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。