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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

【原创】面向对象作业:选课系统中用pickle储存多个对象间组合引用关系的那些坑...

發布時間:2025/3/15 windows 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【原创】面向对象作业:选课系统中用pickle储存多个对象间组合引用关系的那些坑... 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

?

轉載請注明出處https://www.cnblogs.com/oceanicstar/p/9030121.html

?


想直接看結論先提前列出:

  1、存儲一個對象,文件不是真的給你存儲的了對象這種東西,存儲的都是一些代碼而已。

  具體是哪些代碼呢?

  想想看,我們保存對象的目的,是為了方便以后從文件里加載回來時,能讓計算機自動幫你構建回之前的那個對象。那么文件里頭會存儲一些什么代碼呢?

    ①要加載文件時,能夠重構回之前的那個對象,至少能夠實例化出這個對象的類的定義代碼得存儲到文件里頭吧

    ②如果這個類繼承了一些父類的東西,或者跟其他類有組合關系之類的blabla…那么這些類的定義代碼也會儲存到文件里頭

    ③這個對象自己的屬性和方法(在類定義之外自己定義的)得存儲到文件里頭吧

    總之,一切目的都是為了重構時找到所有必須的素材(各種類、函數、變量的定義代碼,還有相互之間的實例化、引用等關系),就跟只有集齊七顆龍珠才能召喚神龍一樣……

  2、我們將每個對象pickle到不同文件后再加載load回來時,pickle反序列化load加載回來都是重構了一個原來對象的副本,pickle文件里存儲了構建出這些對象需要引用的類、方法、對象等引用關系。

  3、如果想要加載回來后的對象組合關系還能對應上的話,是不能把這多個對象分開dump到不同文件的,必要要同時dump到一個文件內。

  4、由于上述原因,實際應用中,我們要儲存多個對象間的組合引用關系,往往需要使用字典/列表/元組等容器來盛放這些有組合關系的對象,然后將這個容器一次dump到一個文件中去。


?

——以下故事純屬虛構,如有雷同,怕是你轉載我的吧!轉載請注明出處,謝謝!

大海:我X,這選課系統咋這么難寫啊,寫了我10幾天,老是有bug出來,明明我保存了老師、學員和班級間的組合關系,怎么加載回來進行值的更改,就不會相互自動關聯地改變值了呢?

流星:要想講明白pickle儲存多個對象之間的組合關系問題,要先從一個面向對象的例子開始說起。。。

大海:啥例子?

流星:是一個簡化了的例子,你看下面

1 class A: 2 def __init__(self, name): 3 self.name = name 4 self.b_list = [] 5 6 7 class B: 8 def __init__(self, name): 9 self.name = name 10 self.a_list = [] 11 12 13 # 各實例化一個對象 14 a1 = A('A類1號') 15 b1 = B('B類1號') 16 17 # 打印 2個實例各自的列表屬性 18 print('-------當前各自的列表屬性-------') 19 print('a1的b_list屬性:', a1.b_list) 20 print('b1的a_list屬性:', b1.a_list) 21 22 # 在實例b1的列表屬性中建立組合關系 23 b1.a_list.append(a1) 24 25 # 打印對象組合關系 26 print('\n-------當前的組合關系-------') 27 print('a1的b_list屬性:', a1.b_list) 28 print('b1的a_list中的A對象的b_list屬性:', b1.a_list[0].b_list) 29 30 # pickle序列化保存到 2個文件里 31 import pickle 32 with open('a1.pk', 'wb') as f1: 33 pickle.dump(a1, f1) 34 with open('b1.pk', 'wb') as f2: 35 pickle.dump(b1, f2)

運行結果

-------當前各自的列表屬性------- a1的b_list屬性: [] b1的a_list屬性: []-------當前的組合關系------- a1的b_list屬性: [] b1的a_list中的A對象的b_list屬性: []

大海:哈哈,我看懂了,b1的a_list列表屬性添加了a1對象,建立了組合關系!

流星:是的,而且我們還把a1對象和b1對象分別存到了文件里頭

大海:(滿懷自信地)對!都用的是pickle序列化dump到文件,再load回來的話,他們組合關系肯定還是在的吧!

流星:是嗎?那么讓我們來驗證一下吧

1 import pickle 2 3 class A: 4 def __init__(self, name): 5 self.name = name 6 self.b_list = [] 7 8 9 class B: 10 def __init__(self, name): 11 self.name = name 12 self.a_list = [] 13 14 # 將a1和b1加載回來 15 with open('a1.pk', 'rb') as f1: 16 a1 = pickle.load(f1) 17 with open('b1.pk', 'rb') as f2: 18 b1 = pickle.load(f2) 19 20 # 打印對象組合關系 21 print('-------加載回來的組合關系-------') 22 print('a1的b_list屬性:', a1.b_list) 23 print('b1的a_list中的A對象的b_list屬性:', b1.a_list[0].b_list) 24 25 # 再實例化一個b2 26 b2 = B('B類2號') 27 a1.b_list.append(b2) 28 29 # 打印對象組合關系 30 print('\n-------給a1列表屬性添加b2后的組合關系-------') 31 print('a1的b_list屬性:', a1.b_list) 32 print('b1的a_list中的A對象的b_list屬性:', b1.a_list[0].b_list)

運行結果

-------加載回來的組合關系------- a1的b_list屬性: [] b1的a_list中的A對象的b_list屬性: []-------給a1列表屬性添加b2后的組合關系------- a1的b_list屬性: [<__main__.B object at 0x00000000025C2A90>] b1的a_list中的A對象的b_list屬性: []

大海:咦?怎么回事?我們不是已經建立了b1和a1間的組合關系嗎?那b1中的a_list里的A類對象就應該是a1啊?

   那么我們給a1對象的b_list屬性列表添加上了對象b2(上面結果確實添加了一個B object對象),

   同樣的b1中的a_list里的A類對象的b_list屬性不也應該添加上對象b2了嗎,為啥上面結果打印結果還是個空列表 [ ] 呢?

   額……不明白……

   到底現在加載回來的a1對象,跟b1中的a_list里的那個A類對象還是同一個么?

流星:那我們打印a1對象和b1的a_list列表屬性看看?

1 # 打印 a1對象 2 print('\na1對象:') 3 print(a1) 4 5 # 打印 b1對象中a_list列表 6 print('b1對象的a_list列表:') 7 print(b1.a_list)

運行結果

a1對象: <__main__.A object at 0x00000000025F24E0>b1對象的a_list列表: [<__main__.A object at 0x000000000367D278>]

大海:#%&*#@()&%#@,X!……果然不是同一個對象了!

   怎么搞的,我們不是把a1和b1這2個對象都pickle了嗎?組合關系怎么亂了呢?

流星:稍等片刻,答案即將揭曉。。。來條華麗的分割線吧


?

大海:這分割線一點都不華麗啊!

流星:……或許等我學完前端就華麗了吧

大海:……

流星:其實,這里首先要理解的是我們將每個對象pickle到不同文件后再加載load回來時,每個對象被恢復到一個與原來對象值相等的對象,但本質上不是同一個對象,而是重構了個新的對象。

   換句話說,每次pickle反序列化load加載回來都是原來對象的一個副本,那么我們從把兩個有組合關系的對象a1和b1分別用pickle序列化dump到兩個文件里頭就是不對的,這樣加載回來的時候,a1和b1對象的屬性也都是在各自文件load加載過程中獨立復制生成的

   具體來解釋,就是:

   a1對象的b_list屬性,在a1.pk文件load加載回來的過程,可以理解為:a1對象加載回來時,計算機開辟一個內存空間放a1對象,發現a1里頭有個b_list屬性啊,值是空列表[ ],好的,那么給他開辟一個內存空間放這個空列表屬性吧

   b1對象的a_list屬性里頭有值,并且是個A類的實例對象(在保存到文件之前的程序里是a1),而在b1.pk文件load加載回來的過程中python重構了一個與A類的實例對象(但不是現在的a1了)間的組合關系,可以理解為:b1對象加載回來時,python要重構一個b1對象,發現b1里頭有個a_list屬性啊,值是個列表,里頭居然還裝了個A類的實例對象,但是這文件里頭沒有說明這個A類的實例對象是誰呀,只告訴我了有個b1對象要返回,這個A類的實例對象返回給誰呢?算了,給b1開辟一個內存空間放這個列表屬性吧,并且去構造一個A類的新的實例對象,這樣至少保留了b1的屬性值不變吧,嗯!就這么干!

   好了,這下a1對象和b1對象都各自加載完成了,但是這樣計算機并沒有把b1對象的a_list屬性中的A類實例對象當成是a1來關聯。。。

大海:那么,怎么才能在pickle序列化保存后,a1與b1間的組合關系還能加載回來呢?

流星:再來條華麗的……

大海:……


流星:其實,上面的pickle保存有個關鍵問題是,有組合關系的多個對象在pickle序列化保存到文件時,如果想要加載回來后的對象組合關系還能對應上的話,是不能把這多個對象分開dump到不同文件的!

   這的原因就像上面解釋的一樣

大海:前面說的太啰嗦,我聽不懂啊!

流星:……

大海:能簡單用人話解釋下可以嗎?

流星:好吧,作為神的我就盡量……

大海:……

流星:其實,當a1和b1對象分開dump到不同的文件時,加載回來是分開獨立加載的,因為pickle反序列化重構對象間的關系是在load方法執行時一次性加載回來生成的,所以在load加載回b1對象時(也就是運行b1 = pickle.load(f2)時),就獨立地把b1對象的關系建立好了,即b1的a_list屬性里頭有組合關系需要關聯的對象的那個A類的實例對象占用的內存地址也分配好了,這個過程是與運行 a1 = pickle.load(f1)相互獨立的,毫無關系,所以load加載回a1對象的內存地址也是另外獨立分配的,也就是說,現在加載回來的b1與a1對象已經沒有組合關系了,跟b1有組合關系的是在運行b1 = pickle.load(f2)時,計算機在內存里自動生成的一個A類的實例對象,這個A類的實例對象被放在了b1的a_list屬性列表里頭。

流星:這下明白了吧……?

大海:好像有點點明白了……可是,那我該怎么做才能在文件里加載回a1與b1組合關系呢?

流星:那就再來一條華……

大海:別來了,算我求你了好嘛?

流星:好吧!那就最后再來一條吧!

大海:……


流星:答案是——把a1與b1dump到同一個文件里頭!

大海:哦,我知道,pickle是可以多次dump到一個文件的!我們可以先把a1對象dump到文件里,然后再把b1對象也dump到同一個文件里頭唄,然后load回來的時候,load兩次對吧,我聰明吧?哈哈~

流星:拉倒吧,你去試試看,這樣能還原回來我們要的組合關系嗎?

大海:肯定行,你等著……這就運行給你看!

1 class A: 2 def __init__(self, name): 3 self.name = name 4 self.b_list = [] 5 6 7 class B: 8 def __init__(self, name): 9 self.name = name 10 self.a_list = [] 11 12 13 # 各實例化一個對象 14 a1 = A('A類1號') 15 b1 = B('B類1號') 16 17 # 打印 2個實例各自的列表屬性 18 print('-------當前各自的列表屬性-------') 19 print('a1的b_list屬性:', a1.b_list) 20 print('b1的a_list屬性:', b1.a_list) 21 22 # 在實例b1的列表屬性中建立組合關系 23 b1.a_list.append(a1) 24 25 # 打印對象組合關系 26 print('\n-------當前的組合關系-------') 27 print('a1的b_list屬性:', a1.b_list) 28 print('b1的a_list中的A對象的b_list屬性:', b1.a_list[0].b_list) 29 30 # pickle序列化分兩次dump到 1個文件里 31 import pickle 32 with open('a1b1.pk', 'wb') as f: 33 pickle.dump(a1, f) 34 pickle.dump(b1, f)

運行結果

-------當前各自的列表屬性------- a1的b_list屬性: [] b1的a_list屬性: []-------當前的組合關系------- a1的b_list屬性: [] b1的a_list中的A對象的b_list屬性: []

大海:嘿嘿,馬上要load回來啦,看好了啊!

1 class A: 2 def __init__(self, name): 3 self.name = name 4 self.b_list = [] 5 6 7 class B: 8 def __init__(self, name): 9 self.name = name 10 self.a_list = [] 11 12 13 # 將a1和b1加載回來 14 import pickle 15 with open('a1b1.pk', 'rb') as f: 16 a1 = pickle.load(f) 17 b1 = pickle.load(f) 18 19 # 打印對象組合關系 20 print('-------加載回來的組合關系-------') 21 print('a1的b_list屬性:', a1.b_list) 22 print('b1的a_list中的A對象的b_list屬性:', b1.a_list[0].b_list) 23 24 # 再實例化一個b2 25 b2 = B('B類2號') 26 a1.b_list.append(b2) 27 28 # 打印對象組合關系 29 print('\n-------給a1列表屬性添加b2后的組合關系-------') 30 print('a1的b_list屬性:', a1.b_list) 31 print('b1的a_list中的A對象的b_list屬性:', b1.a_list[0].b_list) 32 33 # 打印 a1對象 34 print('\na1對象:') 35 print(a1) 36 37 # 打印 b1對象中a_list列表 38 print('\nb1對象的a_list列表:') 39 print(b1.a_list)

運行結果

-------加載回來的組合關系------- a1的b_list屬性: [] b1的a_list中的A對象的b_list屬性: []-------給a1列表屬性添加b2后的組合關系------- a1的b_list屬性: [<__main__.B object at 0x00000000024A32B0>] b1的a_list中的A對象的b_list屬性: []a1對象: <__main__.A object at 0x0000000002571470>b1對象的a_list列表: [<__main__.A object at 0x0000000002572A90>]

大海:我X,怎么還是不行啊!a1對象和b1.a_list里的那個A類實例對象還是不同!到底要怎么才行啊!啊!啊!

流星:大哥,別雞凍……

大海:啊!啊!啊!解決不了問題,我就雞凍!

流星:你雞凍起來也別拍我行嗎?我都快被你拍死了……

大海:啊!啊!啊!怎么回事啊!

流星:我直接告訴你行了吧= =!

大海:你倒是快講啊!

流星:好好好……那就再來一條……

大海:%¥&#@*!¥&*

流星:別拍了,不來了……

大海:快說!

流星:其實答案就是——把a1與b1對象dump到同一個文件里頭!……

大海:你大爺,剛剛不就是這么說的嗎?

流星:那是因為我話還沒說完呢,你就是沒耐性,不等我把話說完你就吵著說明白了……

大海:那你繼續說完啊!

流星:你別打斷我……除了要把a1與b1對象dump到同一個文件里頭,還要保證,是同一次dump命令序列化的

大海:說人話!

流星:= =!我的意思就是,可以把a1與b1合并成一個元組,這樣就可以通過這個元組把a1與b1對象一次性dump到文件里了

大海:我X,這也行啊。。。我怎么沒想到。。。

流星:是啊,不信我運行下你看看。先dump文件……

1 class A: 2 def __init__(self, name): 3 self.name = name 4 self.b_list = [] 5 6 7 class B: 8 def __init__(self, name): 9 self.name = name 10 self.a_list = [] 11 12 13 # 各實例化一個對象 14 a1 = A('A類1號') 15 b1 = B('B類1號') 16 17 # 打印 2個實例各自的列表屬性 18 print('-------當前各自的列表屬性-------') 19 print('a1的b_list屬性:', a1.b_list) 20 print('b1的a_list屬性:', b1.a_list) 21 22 # 在實例b1的列表屬性中建立組合關系 23 b1.a_list.append(a1) 24 25 # 打印對象組合關系 26 print('\n-------當前的組合關系-------') 27 print('a1的b_list屬性:', a1.b_list) 28 print('b1的a_list中的A對象的b_list屬性:', b1.a_list[0].b_list) 29 30 # pickle序列化用元組1次dump到同1個文件 31 import pickle 32 with open('a1b1.pk', 'wb') as f: 33 pickle.dump((a1, b1), f)

流星:打印結果當然還是

-------當前各自的列表屬性------- a1的b_list屬性: [] b1的a_list屬性: []-------當前的組合關系------- a1的b_list屬性: [] b1的a_list中的A對象的b_list屬性: []

流星:再一次load回來

1 class A: 2 def __init__(self, name): 3 self.name = name 4 self.b_list = [] 5 6 7 class B: 8 def __init__(self, name): 9 self.name = name 10 self.a_list = [] 11 12 13 # 將a1和b1加載回來 14 import pickle 15 with open('a1b1.pk', 'rb') as f: 16 a1, b1 = pickle.load(f) 17 18 # 打印對象組合關系 19 print('-------加載回來的組合關系-------') 20 print('a1的b_list屬性:', a1.b_list) 21 print('b1的a_list中的A對象的b_list屬性:', b1.a_list[0].b_list) 22 23 # 再實例化一個b2 24 b2 = B('B類2號') 25 a1.b_list.append(b2) 26 27 # 打印對象組合關系 28 print('\n-------給a1列表屬性添加b2后的組合關系-------') 29 print('a1的b_list屬性:', a1.b_list) 30 print('b1的a_list中的A對象的b_list屬性:', b1.a_list[0].b_list) 31 32 # 打印 a1對象 33 print('\na1對象:') 34 print(a1) 35 36 # 打印 b1對象中a_list列表 37 print('\nb1對象的a_list列表:') 38 print(b1.a_list)

運行結果

-------加載回來的組合關系------- a1的b_list屬性: [] b1的a_list中的A對象的b_list屬性: []-------給a1列表屬性添加b2后的組合關系------- a1的b_list屬性: [<__main__.B object at 0x00000000025D2A90>] b1的a_list中的A對象的b_list屬性: [<__main__.B object at 0x00000000025D2A90>]a1對象: <__main__.A object at 0x00000000025D1470>b1對象的a_list列表: [<__main__.A object at 0x00000000025D1470>]

大海:我去,真的一樣了!牛X!

流星:那必須的!這里同時把a1,b1對象dump到一個文件時,儲存的組合關系是在這個文件里頭的,再load出來返回給新程序的a1和b1對象,也是直接把組合關系指定重構給了新的a1和b1對象,沒有再去單獨開辟內存空間去生成其他的對象了

大海:原來是這樣,厲害!

流星:哈哈,為了慶祝,再來條華麗的分割線吧?

大海:來吧,隨便你來!


流星:其實再來個分割線是為了再舉2個pickle序列化的例子的,或許看了下面2個例子,會更好地理解這個問題

大海:好!你發出來,我研究研究

流星:嗯,先看個列表對象“遞歸引用”的例子

大海:啥?“遞歸引用”?我瞅瞅……

>>> l = [1, 2, 3] >>> l.append(l) >>> l [1, 2, 3, [...]] >>> l[3] [1, 2, 3, [...]] >>> l[3][3] [1, 2, 3, [...]] >>> p = pickle.dumps(l) >>> l2 = pickle.loads(p) >>> l2 [1, 2, 3, [...]] >>> l2[3] [1, 2, 3, [...]] >>> l2[3][3] [1, 2, 3, [...]]

大海:我去,原來列表還能這么玩啊!?

流星:是啊,看過“遞歸引用”了,那么也能有點接受下面這個“循環引用”的例子了吧……

大海:還有……“循環引用”……?

>>> a = [1, 2] >>> b = [3, 4] >>> a.append(b) >>> a [1, 2, [3, 4]] >>> b.append(a) >>> a [1, 2, [3, 4, [...]]] >>> b [3, 4, [1, 2, [...]]] >>> a[2] [3, 4, [1, 2, [...]]] >>> b[2] [1, 2, [3, 4, [...]]] >>> a[2] is b 1 >>> b[2] is a 1 >>> f = file('temp.pkl', 'w') >>> pickle.dump((a, b), f) >>> f.close() >>> f = file('temp.pkl', 'r') >>> c, d = pickle.load(f) >>> f.close() >>> c [1, 2, [3, 4, [...]]] >>> d [3, 4, [1, 2, [...]]] >>> c[2] [3, 4, [1, 2, [...]]] >>> d[2] [1, 2, [3, 4, [...]]] >>> c[2] is d 1 >>> d[2] is c 1

大海:咦,這個“循環引用”也用到了把a、b兩個列表對象合并成一個元組dump到同一個文件里頭啊

流星:是的!之所以這么做,是因為“遞歸引用”和“循環引用”也類似對象間的組合關系,本質都是一個對象與一個對象建立了內存地址的引用(遞歸引用是引用自己)關系。要用元組的形式,同時一次性將2個對象序列化dump到同一個文件,保留指定的2個對象的引用關系,并且反序列化時就能把這個引用關系把指定返回給重構的2個對象了。

大海:原來如此……

流星:嗯,我們可以看看,如果“循環引用”的例子,改成將a與b分2次dump到1個文件里頭,結果會怎樣?

>>> f = file('temp.pkl', 'w') >>> pickle.dump(a, f) >>> pickle.dump(b, f) >>> f.close() >>> f = file('temp.pkl', 'r') >>> c = pickle.load(f) >>> d = pickle.load(f) >>> f.close() >>> c [1, 2, [3, 4, [...]]] >>> d [3, 4, [1, 2, [...]]] >>> c[2] [3, 4, [1, 2, [...]]] >>> d[2] [1, 2, [3, 4, [...]]] >>> c[2] is d 0 >>> d[2] is c 0

流星:你看,這里分2次dump到1個文件的話,第一次由a對象單獨dump進文件的字符串信息load回的對象c,c[2]引用的不再是對象d了(d是由b對象dump進文件的字符串信息load回的),d[2]引用的也不再是對象c了,所以a與b本身的相互引用關系,已經在分開2次dump時丟失掉了。那這里c[2],d[2]引用的是誰呢?是各自從文件load重構成列表對象c和d時,為了保證c[2]和d[2]的值與之前相等,計算機自己用list類重新生成的兩個新的列表對象讓他們各自引用

大海:明白了!

流星:最后,推薦個詳細講解pickle模塊的博客文章:《python pickle模塊》

轉載于:https://www.cnblogs.com/oceanicstar/p/9030121.html

總結

以上是生活随笔為你收集整理的【原创】面向对象作业:选课系统中用pickle储存多个对象间组合引用关系的那些坑...的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。