การบันทึกวัตถุ (การเก็บข้อมูล)


233

ฉันได้สร้างวัตถุแบบนี้:

company1.name = 'banana' 
company1.value = 40

ฉันต้องการบันทึกวัตถุนี้ ฉันจะทำสิ่งนั้นได้อย่างไร


1
ดูตัวอย่างสำหรับผู้ที่มาที่นี่เพื่อดูตัวอย่างวิธีใช้ดอง
Martin Thoma

@MartinThoma: ทำไมคุณ (ดูเหมือน) ต้องการคำตอบที่ได้รับการยอมรับ (ของคำถามที่เชื่อมโยง )?
martineau

protocol=pickle.HIGHEST_PROTOCOLในขณะที่ผมเชื่อมโยงกับคำตอบที่ได้รับการยอมรับไม่ได้ คำตอบของฉันยังให้ทางเลือกในการดอง
Martin Thoma

คำตอบ:


449

คุณสามารถใช้pickleโมดูลในไลบรารีมาตรฐาน นี่คือแอปพลิเคชันระดับต้นสำหรับตัวอย่างของคุณ:

import pickle

class Company(object):
    def __init__(self, name, value):
        self.name = name
        self.value = value

with open('company_data.pkl', 'wb') as output:
    company1 = Company('banana', 40)
    pickle.dump(company1, output, pickle.HIGHEST_PROTOCOL)

    company2 = Company('spam', 42)
    pickle.dump(company2, output, pickle.HIGHEST_PROTOCOL)

del company1
del company2

with open('company_data.pkl', 'rb') as input:
    company1 = pickle.load(input)
    print(company1.name)  # -> banana
    print(company1.value)  # -> 40

    company2 = pickle.load(input)
    print(company2.name) # -> spam
    print(company2.value)  # -> 42

นอกจากนี้คุณยังสามารถกำหนดยูทิลิตี้ง่าย ๆ ของคุณเองดังต่อไปนี้ซึ่งเปิดไฟล์และเขียนวัตถุเดียวไปยังมัน:

def save_object(obj, filename):
    with open(filename, 'wb') as output:  # Overwrites any existing file.
        pickle.dump(obj, output, pickle.HIGHEST_PROTOCOL)

# sample usage
save_object(company1, 'company1.pkl')

ปรับปรุง

เนื่องจากนี่เป็นคำตอบที่ได้รับความนิยมฉันจึงต้องการสัมผัสกับหัวข้อการใช้งานขั้นสูงเล็กน้อย

cPickle(หรือ_pickle) vspickle

มันเกือบจะดีกว่าเสมอที่จะใช้cPickleโมดูลจริงมากกว่าpickleเพราะเดิมเขียนใน C และเร็วกว่ามาก มีความแตกต่างเล็กน้อยระหว่างพวกเขา แต่ในสถานการณ์ส่วนใหญ่พวกเขาเท่ากันและรุ่น C จะให้ประสิทธิภาพที่เหนือกว่าอย่างมาก สลับไปมันไม่อาจจะง่ายเพียงแค่เปลี่ยนimportคำสั่งนี้:

import cPickle as pickle

ใน Python 3 cPickleถูกเปลี่ยนชื่อ_pickleแต่การทำเช่นนี้ไม่จำเป็นอีกต่อไปเนื่องจากpickleโมดูลทำหน้าที่นี้โดยอัตโนมัติ - ดูความแตกต่างระหว่าง pickle และ _pickle ใน python 3 .

บทสรุปคือคุณสามารถใช้สิ่งต่อไปนี้เพื่อให้แน่ใจว่ารหัสของคุณจะใช้เวอร์ชัน C เสมอเมื่อมีให้ใช้งานทั้ง Python 2 และ 3:

try:
    import cPickle as pickle
except ModuleNotFoundError:
    import pickle

รูปแบบสตรีมข้อมูล (โปรโตคอล)

pickleสามารถอ่านและเขียนไฟล์ในรูปแบบที่แตกต่างกันเฉพาะ Python ที่เรียกว่าโปรโตคอลตามที่อธิบายไว้ในเอกสารประกอบ "โปรโตคอลรุ่น 0" คือ ASCII ดังนั้น "มนุษย์อ่านได้" เวอร์ชั่น> 0 เป็นเลขฐานสองและจำนวนสูงสุดที่มีอยู่นั้นขึ้นอยู่กับเวอร์ชันของ Python ที่ใช้อยู่ ค่าเริ่มต้นยังขึ้นอยู่กับเวอร์ชันของ Python ในหลาม 2 เริ่มต้นเป็นรุ่นพิธีสาร0แต่ใน Python 3.8.1 4ก็โปรโตคอลรุ่น ใน Python 3.x โมดูลมีการpickle.DEFAULT_PROTOCOLเพิ่มเข้ามา แต่ไม่มีอยู่ใน Python 2

โชคดีที่มีการจดชวเลขสำหรับการpickle.HIGHEST_PROTOCOLโทรทุกครั้ง (สมมติว่าเป็นสิ่งที่คุณต้องการและคุณมักจะทำ) เพียงใช้หมายเลขตัวอักษร-1- คล้ายกับการอ้างอิงองค์ประกอบสุดท้ายของลำดับผ่านดัชนีลบ ดังนั้นแทนที่จะเขียน:

pickle.dump(obj, output, pickle.HIGHEST_PROTOCOL)

คุณสามารถเขียน:

pickle.dump(obj, output, -1)

ไม่ว่าจะด้วยวิธีใดคุณจะต้องระบุโปรโตคอลเพียงครั้งเดียวหากคุณสร้างPicklerวัตถุเพื่อใช้ในการดำเนินการดองหลายรายการ:

pickler = pickle.Pickler(output, -1)
pickler.dump(obj1)
pickler.dump(obj2)
   etc...

หมายเหตุ : หากคุณอยู่ในสภาพแวดล้อมที่ใช้ Python เวอร์ชันต่าง ๆ คุณอาจต้องการใช้ (เช่น hardcode) หมายเลขโปรโตคอลเฉพาะที่ทุกคนสามารถอ่านได้ (รุ่นที่ใหม่กว่าสามารถอ่านไฟล์ที่ผลิตโดยรุ่นก่อนหน้าได้) .

หลายวัตถุ

ในขณะที่ไฟล์ดองสามารถมีหมายเลขใด ๆ ของวัตถุดองดังแสดงในตัวอย่างข้างต้นเมื่อมีหมายเลขที่ไม่รู้จักพวกเขาก็มักจะง่ายต่อการจัดเก็บพวกเขาทั้งหมดในการจัดเรียงของภาชนะที่แตกขนาดบางเหมือนlist, tupleหรือdictและการเขียน พวกเขาทั้งหมดไปยังไฟล์ในการโทรเดียว:

tech_companies = [
    Company('Apple', 114.18), Company('Google', 908.60), Company('Microsoft', 69.18)
]
save_object(tech_companies, 'tech_companies.pkl')

และกู้คืนรายการและทุกสิ่งในภายหลังด้วย:

with open('tech_companies.pkl', 'rb') as input:
    tech_companies = pickle.load(input)

ได้เปรียบที่สำคัญคือคุณไม่จำเป็นต้องรู้วิธีหลายกรณีที่วัตถุถูกบันทึกไว้ในเพื่อที่จะโหลดพวกเขากลับมาในภายหลัง (แม้ว่าการทำเช่นนั้นโดยไม่มีข้อมูลว่าเป็นไปได้ที่จะต้องมีรหัสเฉพาะเล็กน้อยบางคน) ดูคำตอบของคำถามที่เกี่ยวข้องการบันทึกและการโหลดวัตถุหลาย ๆ อันในไฟล์ดอง? สำหรับรายละเอียดเกี่ยวกับวิธีการต่าง ๆ ในการทำเช่นนี้ โดยส่วนตัวผมชอบ @Lutz Prechelt ของคำตอบที่ดีที่สุด นี่มันปรับให้เข้ากับตัวอย่างที่นี่:

class Company:
    def __init__(self, name, value):
        self.name = name
        self.value = value

def pickled_items(filename):
    """ Unpickle a file of pickled data. """
    with open(filename, "rb") as f:
        while True:
            try:
                yield pickle.load(f)
            except EOFError:
                break

print('Companies in pickle file:')
for company in pickled_items('company_data.pkl'):
    print('  name: {}, value: {}'.format(company.name, company.value))

1
นี้เป็นของหายากกับผมเพราะผมคิดว่าจะมีวิธีที่ง่ายต่อการทำบันทึกวัตถุ ... บางสิ่งบางอย่างเช่น 'saveobject (company1 C: \ mypythonobjects)
Peterstone

4
@ Peterstone: หากคุณต้องการเก็บวัตถุหนึ่งชิ้นคุณจะต้องใช้โค้ดประมาณครึ่งเท่าในตัวอย่างของฉัน - ฉันตั้งใจเขียนวิธีที่ฉันทำเพื่อแสดงให้เห็นว่าสามารถบันทึกวัตถุได้มากกว่าหนึ่งวัตถุ (และอ่านกลับในภายหลัง จาก) ไฟล์เดียวกัน
martineau

1
@ Peterstone มีเหตุผลที่ดีมากสำหรับการแยกความรับผิดชอบ วิธีนี้ไม่มีข้อ จำกัด เกี่ยวกับวิธีการใช้ข้อมูลจากกระบวนการดอง คุณสามารถจัดเก็บลงดิสก์หรือคุณสามารถส่งผ่านการเชื่อมต่อเครือข่ายได้
Harald Scheirich

3
@ smartinaeau นี่เป็นคำตอบของ perstones ที่กล่าวถึงหนึ่งควรมีเพียงฟังก์ชันเดียวในการบันทึกวัตถุลงดิสก์ ความรับผิดชอบของผักดองเพียงเพื่อเปลี่ยนวัตถุเป็นข้อมูลที่สามารถจัดการได้เป็นชิ้น ๆ การเขียนสิ่งต่าง ๆ ลงในไฟล์เป็นความรับผิดชอบของวัตถุไฟล์ โดยการรักษาสิ่งที่แยกหนึ่งช่วยให้สูงขึ้นนำมาใช้ใหม่เช่นความสามารถในการส่งข้อมูลดองข้ามการเชื่อมต่อเครือข่ายหรือเก็บไว้ในฐานข้อมูลความรับผิดชอบทั้งหมดที่แยกต่างหากจากข้อมูลจริง <-> แปลงวัตถุ
แฮรัลด์ Scheirich

1
คุณสามารถลบและcompany1 company2ทำไมคุณไม่ลบCompanyและแสดงว่าเกิดอะไรขึ้น
Mike McKerns

49

ฉันคิดว่ามันเป็นข้อสันนิษฐานที่แข็งแกร่งพอclassสมควรที่จะสมมติว่าวัตถุนั้นเป็น เกิดอะไรขึ้นถ้ามันไม่ได้เป็นclass? นอกจากนี้ยังมีสมมติฐานว่าวัตถุไม่ได้ถูกกำหนดไว้ในล่าม เกิดอะไรขึ้นถ้ามันถูกกำหนดไว้ในล่าม? นอกจากนี้จะเกิดอะไรขึ้นถ้ามีการเพิ่มแอตทริบิวต์แบบไดนามิก เมื่อวัตถุหลามบางตัวมีคุณสมบัติที่เพิ่มเข้ามา__dict__หลังจากการสร้างpickleไม่เคารพการเพิ่มคุณสมบัติเหล่านั้น (เช่นมัน 'ลืม' พวกมันถูกเพิ่มเข้ามา - เนื่องจากpickleอนุกรมเป็นอนุกรมโดยอ้างอิงจากคำจำกัดความของวัตถุ)

ในทุกกรณีpickleและcPickleอาจทำให้คุณล้มเหลวอย่างน่ากลัว

หากคุณต้องการบันทึกobject(สร้างโดยพลการ) ซึ่งคุณมีคุณสมบัติ (เพิ่มในนิยามของวัตถุหรือหลังจากนั้น) ... ทางออกที่ดีที่สุดของคุณคือการใช้dillซึ่งสามารถทำให้เป็นอนุกรมได้เกือบทุกอย่างในงูหลาม

เราเริ่มด้วยคลาส ...

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pickle
>>> class Company:
...     pass
... 
>>> company1 = Company()
>>> company1.name = 'banana'
>>> company1.value = 40
>>> with open('company.pkl', 'wb') as f:
...     pickle.dump(company1, f, pickle.HIGHEST_PROTOCOL)
... 
>>> 

ตอนนี้ปิดตัวลงและเริ่มต้นใหม่ ...

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pickle
>>> with open('company.pkl', 'rb') as f:
...     company1 = pickle.load(f)
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 1378, in load
    return Unpickler(file).load()
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 858, in load
dispatch[key](self)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 1090, in load_global
    klass = self.find_class(module, name)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 1126, in find_class
    klass = getattr(mod, name)
AttributeError: 'module' object has no attribute 'Company'
>>> 

โอ๊ะโอ… pickleจัดการไม่ได้ มาลองdillกัน เราจะโยนวัตถุประเภทอื่น (a lambda) สำหรับการวัดที่ดี

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill       
>>> class Company:
...     pass
... 
>>> company1 = Company()
>>> company1.name = 'banana'
>>> company1.value = 40
>>> 
>>> company2 = lambda x:x
>>> company2.name = 'rhubarb'
>>> company2.value = 42
>>> 
>>> with open('company_dill.pkl', 'wb') as f:
...     dill.dump(company1, f)
...     dill.dump(company2, f)
... 
>>> 

และตอนนี้อ่านไฟล์

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> with open('company_dill.pkl', 'rb') as f:
...     company1 = dill.load(f)
...     company2 = dill.load(f)
... 
>>> company1 
<__main__.Company instance at 0x107909128>
>>> company1.name
'banana'
>>> company1.value
40
>>> company2.name
'rhubarb'
>>> company2.value
42
>>>    

มันได้ผล. เหตุผลpickleล้มเหลวและdillไม่เป็นเช่นนั้นdillถือว่า__main__เป็นโมดูล (ส่วนใหญ่) และยังสามารถกำหนดคำนิยามคลาสดองแทนการดองโดยอ้างอิง (เช่นpickleไม่) เหตุผลที่dillสามารถดองได้lambdaก็คือมันให้ชื่อ ... จากนั้นความมหัศจรรย์สามารถเกิดขึ้นได้

ที่จริงแล้วมีวิธีที่ง่ายกว่าในการบันทึกวัตถุเหล่านี้ทั้งหมดโดยเฉพาะถ้าคุณมีวัตถุจำนวนมากที่คุณสร้างขึ้น เพียงทิ้งเซสชัน python ทั้งหมดและกลับมาใช้ใหม่ในภายหลัง

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> class Company:
...     pass
... 
>>> company1 = Company()
>>> company1.name = 'banana'
>>> company1.value = 40
>>> 
>>> company2 = lambda x:x
>>> company2.name = 'rhubarb'
>>> company2.value = 42
>>> 
>>> dill.dump_session('dill.pkl')
>>> 

ตอนนี้ปิดเครื่องคอมพิวเตอร์ของคุณไปเพลิดเพลินกับเอสเพรสโซหรืออะไรและกลับมาในภายหลัง ...

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> dill.load_session('dill.pkl')
>>> company1.name
'banana'
>>> company1.value
40
>>> company2.name
'rhubarb'
>>> company2.value
42
>>> company2
<function <lambda> at 0x1065f2938>

ข้อเสียเปรียบหลักอย่างเดียวdillคือไม่ได้เป็นส่วนหนึ่งของไลบรารีมาตรฐานของไพ ธ อน ดังนั้นหากคุณไม่สามารถติดตั้งแพ็กเกจหลามบนเซิร์ฟเวอร์ของคุณได้คุณจะไม่สามารถใช้งานได้

แต่ถ้าคุณมีความสามารถในการติดตั้งแพคเกจหลามในระบบของคุณคุณจะได้รับล่าสุดกับdill และคุณจะได้รับการปล่อยตัวออกมารุ่นล่าสุดgit+https://github.com/uqfoundation/dill.git@master#egg=dillpip install dill


ฉันได้รับTypeError: __new__() takes at least 2 arguments (1 given)เมื่อพยายามใช้dill(ซึ่งดูมีแนวโน้ม) กับวัตถุที่ค่อนข้างซับซ้อนซึ่งรวมถึงไฟล์เสียง
MikeiLL

1
@MikeiLL: คุณได้รับTypeErrorเมื่อคุณทำอะไรใช่มั้ย โดยปกติแล้วจะเป็นสัญญาณของการมีจำนวนอาร์กิวเมนต์ที่ไม่ถูกต้องเมื่อสร้างอินสแตนซ์ของคลาส หากนี่ไม่ใช่ส่วนหนึ่งของเวิร์กโฟลว์ของคำถามข้างต้นคุณสามารถโพสต์เป็นคำถามอื่นส่งมาให้ฉันทางอีเมลหรือเพิ่มเป็นปัญหาในdillหน้า GitHub?
Mike McKerns

3
สำหรับใครก็ตามที่ติดตามนี่คือคำถามที่เกี่ยวข้อง @MikeLL ที่โพสต์ - จากคำตอบดูเหมือนว่าจะไม่ใช่dillปัญหา
martineau

dill ให้ฉันMemoryErrorสิ! เพื่อไม่cPickle, และpickle hickle
Färid Alijani

4

คุณสามารถใช้anycacheเพื่อทำงานให้คุณ มันจะพิจารณารายละเอียดทั้งหมด:

  • มันใช้ดิลล์เป็นแบ็กเอนด์ซึ่งขยายpickleโมดูลหลามเพื่อจัดการlambdaและคุณสมบัติหลามที่ดีทั้งหมด
  • มันจัดเก็บวัตถุที่แตกต่างไปยังไฟล์ต่าง ๆ และทำการโหลดใหม่อย่างถูกต้อง
  • จำกัด ขนาดแคช
  • ช่วยให้การล้างแคช
  • อนุญาตให้แชร์วัตถุระหว่างการวิ่งหลายครั้ง
  • อนุญาตการเคารพไฟล์อินพุตที่มีผลต่อผลลัพธ์

สมมติว่าคุณมีฟังก์ชันmyfuncที่สร้างอินสแตนซ์:

from anycache import anycache

class Company(object):
    def __init__(self, name, value):
        self.name = name
        self.value = value

@anycache(cachedir='/path/to/your/cache')    
def myfunc(name, value)
    return Company(name, value)

Anycache เรียกใช้myfuncในครั้งแรกและดองผลลัพธ์ลงในไฟล์cachedirโดยใช้ตัวระบุที่ไม่ซ้ำกัน (ขึ้นอยู่กับชื่อฟังก์ชันและอาร์กิวเมนต์) เป็นชื่อไฟล์ ในการรันต่อเนื่องใด ๆ วัตถุที่ถูกดองจะถูกโหลด หากcachedirถูกเก็บรักษาไว้ระหว่างการทำงานของงูหลามวัตถุที่ถูกดองจะถูกนำมาจากการทำงานของหลามก่อนหน้า

สำหรับรายละเอียดเพิ่มเติมโปรดดูเอกสารประกอบ


เราจะใช้anycacheเพื่อบันทึกมากกว่าหนึ่งอินสแตนซ์ของพูดclassหรือคอนเทนเนอร์เช่น a list(นั่นไม่ใช่ผลลัพธ์ของการเรียกฟังก์ชัน) ได้อย่างไร
martineau

2

ตัวอย่างด่วนที่ใช้company1จากคำถามของคุณด้วย python3

import pickle

# Save the file
pickle.dump(company1, file = open("company1.pickle", "wb"))

# Reload the file
company1_reloaded = pickle.load(open("company1.pickle", "rb"))

อย่างไรก็ตามตามที่ระบุไว้คำตอบดองมักล้มเหลว dillดังนั้นคุณควรใช้

import dill

# Save the file
dill.dump(company1, file = open("company1.pickle", "wb"))

# Reload the file
company1_reloaded = dill.load(open("company1.pickle", "rb"))
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.