Lưu một đối tượng (Kiên trì dữ liệu)


233

Tôi đã tạo một đối tượng như thế này:

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

Tôi muốn lưu đối tượng này. Làm thế nào tôi có thể làm điều đó?


1
Xem ví dụ cho những người đến đây để biết ví dụ đơn giản về cách sử dụng dưa chua.
Martin Thoma

@MartinThoma: Tại sao bạn (dường như) thích câu trả lời đó cho câu trả lời được chấp nhận (của câu hỏi được liên kết )?
martineau

Tại thời điểm tôi liên kết, câu trả lời được chấp nhận không có protocol=pickle.HIGHEST_PROTOCOL. Câu trả lời của tôi cũng đưa ra lựa chọn thay thế cho dưa chua.
Martin Thoma

Câu trả lời:


449

Bạn có thể sử dụng picklemô-đun trong thư viện tiêu chuẩn. Đây là một ứng dụng cơ bản của nó cho ví dụ của bạn:

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

Bạn cũng có thể định nghĩa tiện ích đơn giản của riêng mình như sau sẽ mở tệp và ghi một đối tượng vào đó:

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')

Cập nhật

Vì đây là một câu trả lời phổ biến, tôi muốn chạm vào một vài chủ đề sử dụng hơi nâng cao.

cPickle(hoặc _pickle) so vớipickle

Hầu như luôn luôn thích sử dụng cPicklemô-đun hơn là picklevì trước đây được viết bằng C và nhanh hơn nhiều. Có một số khác biệt tinh tế giữa chúng, nhưng trong hầu hết các tình huống chúng tương đương và phiên bản C sẽ cung cấp hiệu suất vượt trội hơn rất nhiều. Chuyển sang nó không thể dễ dàng hơn, chỉ cần thay đổi importtuyên bố này:

import cPickle as pickle

Trong Python 3, cPickleđã được đổi tên _pickle, nhưng thực hiện điều này không còn cần thiết nữa vì picklebây giờ mô-đun tự động có thể thấy sự khác biệt giữa dưa chua và _pickle trong python 3? .

Tóm tắt là bạn có thể sử dụng một cái gì đó như sau để đảm bảo rằng mã của bạn sẽ luôn sử dụng phiên bản C khi nó có sẵn trong cả Python 2 và 3:

try:
    import cPickle as pickle
except ModuleNotFoundError:
    import pickle

Định dạng luồng dữ liệu (giao thức)

picklecó thể đọc và ghi các tệp theo nhiều định dạng khác nhau, dành riêng cho Python, được gọi là các giao thức như được mô tả trong tài liệu , "Giao thức phiên bản 0" là ASCII và do đó "có thể đọc được". Các phiên bản> 0 là nhị phân và phiên bản cao nhất có sẵn tùy thuộc vào phiên bản Python nào đang được sử dụng. Mặc định cũng phụ thuộc vào phiên bản Python. Trong Python 2, mặc định là phiên bản Giao thức 0, nhưng trong Python 3.8.1, đó là phiên bản Giao thức 4. Trong Python 3.x, mô-đun đã được pickle.DEFAULT_PROTOCOLthêm vào nó, nhưng điều đó không tồn tại trong Python 2.

May mắn thay, có tốc ký để viết pickle.HIGHEST_PROTOCOLtrong mỗi cuộc gọi (giả sử đó là những gì bạn muốn và bạn thường làm), chỉ cần sử dụng số bằng chữ -1- tương tự như tham chiếu phần tử cuối cùng của chuỗi thông qua chỉ số âm. Vì vậy, thay vì viết:

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

Bạn chỉ có thể viết:

pickle.dump(obj, output, -1)

Dù bằng cách nào, bạn chỉ chỉ định giao thức một lần nếu bạn đã tạo một Picklerđối tượng để sử dụng trong nhiều thao tác dưa chua:

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

Lưu ý : Nếu bạn ở trong môi trường chạy các phiên bản Python khác nhau, thì có lẽ bạn sẽ muốn sử dụng rõ ràng (tức là mã cứng) một số giao thức cụ thể mà tất cả chúng có thể đọc (các phiên bản sau thường có thể đọc các tệp được tạo bởi các phiên bản trước đó) .

Nhiều đối tượng

Trong khi một file dưa có thể chứa bất kỳ số đối tượng ngâm, như thể hiện trong các mẫu ở trên, khi có một số không rõ trong số họ, nó thường dễ dàng hơn để lưu trữ tất cả chúng trong một số loại của container variably cỡ, giống như một list, tuplehoặc dictvà ghi tất cả chúng vào tập tin trong một cuộc gọi duy nhất:

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

và khôi phục danh sách và mọi thứ trong đó sau:

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

Ưu điểm chính là bạn không cần biết có bao nhiêu trường hợp đối tượng được lưu để tải lại chúng sau này (mặc dù làm như vậy mà không có thông tin đó có thể, nó yêu cầu một số mã chuyên dụng hơi). Xem câu trả lời cho câu hỏi liên quan Lưu và tải nhiều đối tượng trong tệp dưa chua? để biết chi tiết về các cách khác nhau để làm điều này. Cá nhân tôi thích câu trả lời của @Lutz Prechelt nhất. Đây là nó thích nghi với các ví dụ ở đây:

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
Điều này rất hiếm với tôi bởi vì tôi tưởng tượng sẽ có một cách dễ dàng hơn để cứu một đối tượng ... Một cái gì đó như 'saveobject (company1, c: \ mypythonobjects)
Peterstone

4
@Peterstone: Nếu bạn chỉ muốn lưu trữ một đối tượng, bạn sẽ chỉ cần khoảng một nửa mã như trong ví dụ của tôi - Tôi cố tình viết nó theo cách tôi đã làm để hiển thị có thể lưu nhiều hơn một đối tượng (và sau đó đọc lại từ) cùng một tập tin.
martineau

1
@Peterstone, có một lý do rất tốt cho việc phân chia trách nhiệm. Cách này không có giới hạn về cách dữ liệu từ quá trình tẩy đang được sử dụng. Bạn có thể lưu nó vào đĩa hoặc bạn cũng có thể gửi nó qua kết nối mạng.
Harald Scheirich

3
@martinaeau, đây là câu trả lời cho nhận xét của mọi người về một người nên chỉ có một chức năng để lưu một đối tượng vào đĩa. Trách nhiệm của dưa chua chỉ là biến một đối tượng thành dữ liệu có thể được xử lý như một khối. Viết những thứ để tập tin là trách nhiệm đối tượng tập tin. Bằng cách giữ mọi thứ tách biệt, cho phép tái sử dụng cao hơn, ví dụ như có thể gửi dữ liệu được chọn qua kết nối mạng hoặc lưu trữ trong cơ sở dữ liệu, tất cả các trách nhiệm tách biệt với dữ liệu thực tế <-> chuyển đổi đối tượng
Harald Scheirich

1
Bạn xóa company1company2. Tại sao bạn cũng không xóa Companyvà hiển thị những gì xảy ra?
Mike McKerns

49

Tôi nghĩ rằng đó là một giả định khá mạnh mẽ để cho rằng đối tượng là một class. Nếu nó không phải là một class? Cũng có giả định rằng đối tượng không được xác định trong trình thông dịch. Điều gì nếu nó được định nghĩa trong trình thông dịch? Ngoài ra, nếu các thuộc tính được thêm động thì sao? Khi một số đối tượng python có các thuộc tính được thêm vào __dict__sau khi tạo, picklechúng không tôn trọng việc thêm các thuộc tính đó (tức là 'quên' chúng đã được thêm vào - bởi vì pickletuần tự hóa bằng cách tham chiếu đến định nghĩa đối tượng).

Trong tất cả các trường hợp, picklecPicklecó thể thất bại bạn khủng khiếp.

Nếu bạn đang tìm cách lưu object(được tạo tùy ý), trong đó bạn có các thuộc tính (được thêm vào trong định nghĩa đối tượng hoặc sau đó), đặt cược tốt nhất của bạn là sử dụng dill, có thể tuần tự hóa hầu hết mọi thứ trong python.

Chúng tôi bắt đầu với một lớp học

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)
... 
>>> 

Bây giờ hãy tắt và khởi động lại ...

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'
>>> 

Rất tiếc picklekhông thể xử lý nó. Hãy thử xem dill. Chúng tôi sẽ ném vào một loại đối tượng khác (a lambda) cho biện pháp tốt.

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)
... 
>>> 

Và bây giờ đọc các tập tin.

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
>>>    

Nó hoạt động. Lý do picklethất bại, và dillkhông, là dillđối xử __main__như một mô-đun (đối với hầu hết các phần), và cũng có thể chọn các định nghĩa lớp thay vì chọn theo tham chiếu (như picklekhông). Lý do dillcó thể gây rắc rối lambdalà vì nó mang lại cho nó một cái tên, sau đó phép thuật ngâm có thể xảy ra.

Trên thực tế, có một cách dễ dàng hơn để lưu tất cả các đối tượng này, đặc biệt là nếu bạn có nhiều đối tượng bạn đã tạo. Chỉ cần đổ toàn bộ phiên python, và quay lại sau.

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')
>>> 

Bây giờ hãy tắt máy tính của bạn, thưởng thức espresso hoặc bất cứ thứ gì, và quay lại sau ...

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>

Hạn chế lớn duy nhất dilllà không phải là một phần của thư viện chuẩn python. Vì vậy, nếu bạn không thể cài đặt gói python trên máy chủ của mình, thì bạn không thể sử dụng nó.

Tuy nhiên, nếu bạn có thể cài đặt các gói python trên hệ thống của mình, bạn có thể nhận được bản mới nhất dillvới git+https://github.com/uqfoundation/dill.git@master#egg=dill. Và bạn có thể nhận được phiên bản phát hành mới nhất với pip install dill.


Tôi nhận được TypeError: __new__() takes at least 2 arguments (1 given)khi cố gắng sử dụng dill(có vẻ đầy hứa hẹn) với một đối tượng khá phức tạp bao gồm tệp âm thanh.
MikeiLL

1
@MikeiLL: Bạn đang nhận được một TypeErrorkhi bạn làm gì, chính xác? Đó thường là dấu hiệu của việc có số lượng đối số sai khi khởi tạo một thể hiện của lớp. Nếu đây không phải là một phần của quy trình làm việc của câu hỏi trên, bạn có thể đăng nó dưới dạng câu hỏi khác không, gửi cho tôi qua email hoặc thêm nó dưới dạng một vấn đề trên dilltrang github?
Mike McKerns

3
Đối với bất kỳ ai theo dõi, đây là câu hỏi liên quan @MikeLL đã đăng - từ câu trả lời, rõ ràng đây không phải là dillvấn đề.
martineau 2/2/2015

dilTôi cho tôi MemoryErrormặc dù! cũng vậy cPickle, picklehickle.
Färid Alijani

4

Bạn có thể sử dụng anycache để thực hiện công việc cho bạn. Nó xem xét tất cả các chi tiết:

  • Nó sử dụng thì là phụ trợ, mở rộng picklemô-đun python để xử lý lambdavà tất cả các tính năng python đẹp.
  • Nó lưu trữ các đối tượng khác nhau vào các tệp khác nhau và tải lại chúng đúng cách.
  • Giới hạn kích thước bộ đệm
  • Cho phép xóa bộ nhớ cache
  • Cho phép chia sẻ các đối tượng giữa nhiều lần chạy
  • Cho phép tôn trọng các tập tin đầu vào ảnh hưởng đến kết quả

Giả sử bạn có một hàm myfunctạo cá thể:

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 gọi myfunclần đầu tiên và chọn kết quả cho một tệp cachedirbằng cách sử dụng một mã định danh duy nhất (tùy thuộc vào tên hàm và đối số của nó) làm tên tệp. Trên bất kỳ lần chạy liên tiếp nào, đối tượng ngâm được tải. Nếu cachedirđược bảo tồn giữa các lần chạy trăn, đối tượng được lấy sẽ được lấy từ lần chạy trăn trước đó.

Để biết thêm chi tiết, xem tài liệu


Làm thế nào một người có thể sử dụng anycacheđể lưu nhiều hơn một ví dụ, ví dụ, classhoặc một thùng chứa, chẳng hạn như list(đó không phải là kết quả của việc gọi hàm)?
martineau

2

Ví dụ nhanh sử dụng company1từ câu hỏi của bạn, với 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"))

Tuy nhiên, như câu trả lời này lưu ý, dưa chua thường thất bại. Vì vậy, bạn nên thực sự sử dụng 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"))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.