Keep persistent variables in memory between runs of Python script
Is there any way of keeping a result variable in memory so I don’t have to recalculate it each time I run the beginning of my script? I am doing a long (5-10 sec) series of the exact operations on a data set (which I am reading from disk) every time I run my script. This wouldn’t be too much of a problem since I’m pretty good at using the interactive editor to debug my code in between runs; however sometimes the interactive capabilities just don’t cut it.
I know I could write my results to a file on disk, but I’d like to avoid doing so if at all possible. This should be a solution which generates a variable the first time I run the script, and keeps it in memory until the shell itself is closed or until I explicitly tell it to fizzle out. Something like this:
I’ve an inkling that the shelve module might be what I’m looking for here, but looks like in order to open a shelve variable I would have to specify a file name for the persistent object, and so I’m not sure if it’s quite what I’m looking for.
Any tips on getting shelve to do what I want it to do? Any alternative ideas?
8 Answers 8
You can achieve something like this using the reload global function to re-execute your main script’s code. You will need to write a wrapper script that imports your main script, asks it for the variable it wants to cache, caches a copy of that within the wrapper script’s module scope, and then when you want (when you hit ENTER on stdin or whatever), it calls reload(yourscriptmodule) but this time passes it the cached object such that yourscript can bypass the expensive computation. Here’s a quick example.
wrapper.py
mainscript.py
While wrapper.py is running, you can edit mainscript.py , add new code to the part2 function and be able to run your new code against the pre-computed part1Cache.
To keep data in memory, the process must keep running. Memory belongs to the process running the script, NOT to the shell. The shell cannot hold memory for you.
So if you want to change your code and keep your process running, you’ll have to reload the modules when they’re changed. If any of the data in memory is an instance of a class that changes, you’ll have to find a way to convert it to an instance of the new class. It’s a bit of a mess. Not many languages were ever any good at this kind of hot patching (Common Lisp comes to mind), and there are a lot of chances for things to go wrong.
If you only want to persist one object (or object graph) for future sessions, the shelve module probably is overkill. Just pickle the object you care about. Do the work and save the pickle if you have no pickle-file, or load the pickle-file if you have one.
Python’s shelve is a persistence solution for pickled (serialized) objects and is file-based. The advantage is that it stores Python objects directly, meaning the API is pretty simple.
If you really want to avoid the disk, the technology you are looking for is a «in-memory database.» Several alternatives exist, see this SO question: in-memory database in Python.
Weirdly, none of the earlier answers here mention simple text files. The OP says they don’t like the idea, but as this is becoming a canonical for duplicates which might not have that constraint, this alternative deserves a mention. If all you need is for some text to survive between invocations of your script, save it in a regular text file.
This easily extends to more complex data structures, though then you’ll probably need to use a standard structured format like JSON or YAML (for serializing data with tree-like structures into text) or CSV (for a matrix of columns and rows containing text and/or numbers).
Ultimately, shelve and pickle are just glorified generalized versions of the same idea; but if your needs are modest, the benefits of a simple textual format which you can inspect and update in a regular text editor, and read and manipulate with ubiquitous standard tools, and easily copy and share between different Python versions and even other programming languages as well as version control systems etc, are quite compelling.
As an aside, character encoding issues are a complication which you need to plan for; but in this day and age, just use UTF-8 for all your text files.
Another caveat is that beginners are often confused about where to save the file. A common convention is to save it in the invoking user’s home directory, though that obviously means multiple users cannot share this data. Another is to save it in a shared location, but this then requires an administrator to separately grant write access to this location (except I guess on Windows; but that then comes with its own tectonic plate of other problems).
The main drawback is that text is brittle if you need multiple processes to update the file in rapid succession, and slow to handle if you have lots of data and need to update parts of it frequently. For these use cases, maybe look at a database (probably start with SQLite which is robust and nimble, and included in the Python standard library; scale up to Postgres or etc if you have entrerprise-grade needs).
And, of course, if you need to store native Python structures, shelve and pickle are still there.
pickle — Python object serialization¶
The pickle module implements binary protocols for serializing and de-serializing a Python object structure. “Pickling” is the process whereby a Python object hierarchy is converted into a byte stream, and “unpickling” is the inverse operation, whereby a byte stream (from a binary file or bytes-like object ) is converted back into an object hierarchy. Pickling (and unpickling) is alternatively known as “serialization”, “marshalling,” 1 or “flattening”; however, to avoid confusion, the terms used here are “pickling” and “unpickling”.
The pickle module is not secure. Only unpickle data you trust.
It is possible to construct malicious pickle data which will execute arbitrary code during unpickling. Never unpickle data that could have come from an untrusted source, or that could have been tampered with.
Consider signing data with hmac if you need to ensure that it has not been tampered with.
Safer serialization formats such as json may be more appropriate if you are processing untrusted data. See Comparison with json .
Relationship to other Python modules¶
Comparison with marshal ¶
Python has a more primitive serialization module called marshal , but in general pickle should always be the preferred way to serialize Python objects. marshal exists primarily to support Python’s .pyc files.
The pickle module differs from marshal in several significant ways:
The pickle module keeps track of the objects it has already serialized, so that later references to the same object won’t be serialized again. marshal doesn’t do this.
This has implications both for recursive objects and object sharing. Recursive objects are objects that contain references to themselves. These are not handled by marshal, and in fact, attempting to marshal recursive objects will crash your Python interpreter. Object sharing happens when there are multiple references to the same object in different places in the object hierarchy being serialized. pickle stores such objects only once, and ensures that all other references point to the master copy. Shared objects remain shared, which can be very important for mutable objects.
marshal cannot be used to serialize user-defined classes and their instances. pickle can save and restore class instances transparently, however the class definition must be importable and live in the same module as when the object was stored.
The marshal serialization format is not guaranteed to be portable across Python versions. Because its primary job in life is to support .pyc files, the Python implementers reserve the right to change the serialization format in non-backwards compatible ways should the need arise. The pickle serialization format is guaranteed to be backwards compatible across Python releases provided a compatible pickle protocol is chosen and pickling and unpickling code deals with Python 2 to Python 3 type differences if your data is crossing that unique breaking change language boundary.
Comparison with json ¶
There are fundamental differences between the pickle protocols and JSON (JavaScript Object Notation):
JSON is a text serialization format (it outputs unicode text, although most of the time it is then encoded to utf-8 ), while pickle is a binary serialization format;
JSON is human-readable, while pickle is not;
JSON is interoperable and widely used outside of the Python ecosystem, while pickle is Python-specific;
JSON, by default, can only represent a subset of the Python built-in types, and no custom classes; pickle can represent an extremely large number of Python types (many of them automatically, by clever usage of Python’s introspection facilities; complex cases can be tackled by implementing specific object APIs );
Unlike pickle, deserializing untrusted JSON does not in itself create an arbitrary code execution vulnerability.
The json module: a standard library module allowing JSON serialization and deserialization.
Data stream format¶
The data format used by pickle is Python-specific. This has the advantage that there are no restrictions imposed by external standards such as JSON or XDR (which can’t represent pointer sharing); however it means that non-Python programs may not be able to reconstruct pickled Python objects.
By default, the pickle data format uses a relatively compact binary representation. If you need optimal size characteristics, you can efficiently compress pickled data.
The module pickletools contains tools for analyzing data streams generated by pickle . pickletools source code has extensive comments about opcodes used by pickle protocols.
There are currently 6 different protocols which can be used for pickling. The higher the protocol used, the more recent the version of Python needed to read the pickle produced.
Protocol version 0 is the original “human-readable” protocol and is backwards compatible with earlier versions of Python.
Protocol version 1 is an old binary format which is also compatible with earlier versions of Python.
Protocol version 2 was introduced in Python 2.3. It provides much more efficient pickling of new-style classes . Refer to PEP 307 for information about improvements brought by protocol 2.
Protocol version 3 was added in Python 3.0. It has explicit support for bytes objects and cannot be unpickled by Python 2.x. This was the default protocol in Python 3.0–3.7.
Protocol version 4 was added in Python 3.4. It adds support for very large objects, pickling more kinds of objects, and some data format optimizations. It is the default protocol starting with Python 3.8. Refer to PEP 3154 for information about improvements brought by protocol 4.
Protocol version 5 was added in Python 3.8. It adds support for out-of-band data and speedup for in-band data. Refer to PEP 574 for information about improvements brought by protocol 5.
Serialization is a more primitive notion than persistence; although pickle reads and writes file objects, it does not handle the issue of naming persistent objects, nor the (even more complicated) issue of concurrent access to persistent objects. The pickle module can transform a complex object into a byte stream and it can transform the byte stream into an object with the same internal structure. Perhaps the most obvious thing to do with these byte streams is to write them onto a file, but it is also conceivable to send them across a network or store them in a database. The shelve module provides a simple interface to pickle and unpickle objects on DBM-style database files.
Module Interface¶
To serialize an object hierarchy, you simply call the dumps() function. Similarly, to de-serialize a data stream, you call the loads() function. However, if you want more control over serialization and de-serialization, you can create a Pickler or an Unpickler object, respectively.
The pickle module provides the following constants:
An integer, the highest protocol version available. This value can be passed as a protocol value to functions dump() and dumps() as well as the Pickler constructor.
An integer, the default protocol version used for pickling. May be less than HIGHEST_PROTOCOL . Currently the default protocol is 4, first introduced in Python 3.4 and incompatible with previous versions.
Changed in version 3.0: The default protocol is 3.
Changed in version 3.8: The default protocol is 4.
The pickle module provides the following functions to make the pickling process more convenient:
pickle. dump ( obj , file , protocol = None , * , fix_imports = True , buffer_callback = None ) ¶
Write the pickled representation of the object obj to the open file object file. This is equivalent to Pickler(file, protocol).dump(obj) .
Arguments file, protocol, fix_imports and buffer_callback have the same meaning as in the Pickler constructor.
Changed in version 3.8: The buffer_callback argument was added.
Return the pickled representation of the object obj as a bytes object, instead of writing it to a file.
Arguments protocol, fix_imports and buffer_callback have the same meaning as in the Pickler constructor.
Changed in version 3.8: The buffer_callback argument was added.
Read the pickled representation of an object from the open file object file and return the reconstituted object hierarchy specified therein. This is equivalent to Unpickler(file).load() .
The protocol version of the pickle is detected automatically, so no protocol argument is needed. Bytes past the pickled representation of the object are ignored.
Arguments file, fix_imports, encoding, errors, strict and buffers have the same meaning as in the Unpickler constructor.
Changed in version 3.8: The buffers argument was added.
Return the reconstituted object hierarchy of the pickled representation data of an object. data must be a bytes-like object .
The protocol version of the pickle is detected automatically, so no protocol argument is needed. Bytes past the pickled representation of the object are ignored.
Arguments fix_imports, encoding, errors, strict and buffers have the same meaning as in the Unpickler constructor.
Changed in version 3.8: The buffers argument was added.
The pickle module defines three exceptions:
exception pickle. PickleError ¶
Common base class for the other pickling exceptions. It inherits Exception .
exception pickle. PicklingError ¶
Error raised when an unpicklable object is encountered by Pickler . It inherits PickleError .
Refer to What can be pickled and unpickled? to learn what kinds of objects can be pickled.
exception pickle. UnpicklingError ¶
Error raised when there is a problem unpickling an object, such as a data corruption or a security violation. It inherits PickleError .
Note that other exceptions may also be raised during unpickling, including (but not necessarily limited to) AttributeError, EOFError, ImportError, and IndexError.
The pickle module exports three classes, Pickler , Unpickler and PickleBuffer :
class pickle. Pickler ( file , protocol = None , * , fix_imports = True , buffer_callback = None ) ¶
This takes a binary file for writing a pickle data stream.
The optional protocol argument, an integer, tells the pickler to use the given protocol; supported protocols are 0 to HIGHEST_PROTOCOL . If not specified, the default is DEFAULT_PROTOCOL . If a negative number is specified, HIGHEST_PROTOCOL is selected.
The file argument must have a write() method that accepts a single bytes argument. It can thus be an on-disk file opened for binary writing, an io.BytesIO instance, or any other custom object that meets this interface.
If fix_imports is true and protocol is less than 3, pickle will try to map the new Python 3 names to the old module names used in Python 2, so that the pickle data stream is readable with Python 2.
If buffer_callback is None (the default), buffer views are serialized into file as part of the pickle stream.
If buffer_callback is not None, then it can be called any number of times with a buffer view. If the callback returns a false value (such as None), the given buffer is out-of-band ; otherwise the buffer is serialized in-band, i.e. inside the pickle stream.
It is an error if buffer_callback is not None and protocol is None or smaller than 5.
Changed in version 3.8: The buffer_callback argument was added.
Write the pickled representation of obj to the open file object given in the constructor.
Do nothing by default. This exists so a subclass can override it.
If persistent_id() returns None , obj is pickled as usual. Any other value causes Pickler to emit the returned value as a persistent ID for obj. The meaning of this persistent ID should be defined by Unpickler.persistent_load() . Note that the value returned by persistent_id() cannot itself have a persistent ID.
See Persistence of External Objects for details and examples of uses.
A pickler object’s dispatch table is a registry of reduction functions of the kind which can be declared using copyreg.pickle() . It is a mapping whose keys are classes and whose values are reduction functions. A reduction function takes a single argument of the associated class and should conform to the same interface as a __reduce__() method.
By default, a pickler object will not have a dispatch_table attribute, and it will instead use the global dispatch table managed by the copyreg module. However, to customize the pickling for a specific pickler object one can set the dispatch_table attribute to a dict-like object. Alternatively, if a subclass of Pickler has a dispatch_table attribute then this will be used as the default dispatch table for instances of that class.
See Dispatch Tables for usage examples.
New in version 3.3.
Special reducer that can be defined in Pickler subclasses. This method has priority over any reducer in the dispatch_table . It should conform to the same interface as a __reduce__() method, and can optionally return NotImplemented to fallback on dispatch_table -registered reducers to pickle obj .
New in version 3.8.
Deprecated. Enable fast mode if set to a true value. The fast mode disables the usage of memo, therefore speeding the pickling process by not generating superfluous PUT opcodes. It should not be used with self-referential objects, doing otherwise will cause Pickler to recurse infinitely.
Use pickletools.optimize() if you need more compact pickles.
class pickle. Unpickler ( file , * , fix_imports = True , encoding = ‘ASCII’ , errors = ‘strict’ , buffers = None ) ¶
This takes a binary file for reading a pickle data stream.
The protocol version of the pickle is detected automatically, so no protocol argument is needed.
The argument file must have three methods, a read() method that takes an integer argument, a readinto() method that takes a buffer argument and a readline() method that requires no arguments, as in the io.BufferedIOBase interface. Thus file can be an on-disk file opened for binary reading, an io.BytesIO object, or any other custom object that meets this interface.
The optional arguments fix_imports, encoding and errors are used to control compatibility support for pickle stream generated by Python 2. If fix_imports is true, pickle will try to map the old Python 2 names to the new names used in Python 3. The encoding and errors tell pickle how to decode 8-bit string instances pickled by Python 2; these default to ‘ASCII’ and ‘strict’, respectively. The encoding can be ‘bytes’ to read these 8-bit string instances as bytes objects. Using encoding=’latin1′ is required for unpickling NumPy arrays and instances of datetime , date and time pickled by Python 2.
If buffers is None (the default), then all data necessary for deserialization must be contained in the pickle stream. This means that the buffer_callback argument was None when a Pickler was instantiated (or when dump() or dumps() was called).
If buffers is not None, it should be an iterable of buffer-enabled objects that is consumed each time the pickle stream references an out-of-band buffer view. Such buffers have been given in order to the buffer_callback of a Pickler object.
Changed in version 3.8: The buffers argument was added.
Read the pickled representation of an object from the open file object given in the constructor, and return the reconstituted object hierarchy specified therein. Bytes past the pickled representation of the object are ignored.
Raise an UnpicklingError by default.
If defined, persistent_load() should return the object specified by the persistent ID pid. If an invalid persistent ID is encountered, an UnpicklingError should be raised.
See Persistence of External Objects for details and examples of uses.
Import module if necessary and return the object called name from it, where the module and name arguments are str objects. Note, unlike its name suggests, find_class() is also used for finding functions.
Subclasses may override this to gain control over what type of objects and how they can be loaded, potentially reducing security risks. Refer to Restricting Globals for details.
Raises an auditing event pickle.find_class with arguments module , name .
class pickle. PickleBuffer ( buffer ) ¶
A wrapper for a buffer representing picklable data. buffer must be a buffer-providing object, such as a bytes-like object or a N-dimensional array.
PickleBuffer is itself a buffer provider, therefore it is possible to pass it to other APIs expecting a buffer-providing object, such as memoryview .
PickleBuffer objects can only be serialized using pickle protocol 5 or higher. They are eligible for out-of-band serialization .
New in version 3.8.
Return a memoryview of the memory area underlying this buffer. The returned object is a one-dimensional, C-contiguous memoryview with format B (unsigned bytes). BufferError is raised if the buffer is neither C- nor Fortran-contiguous.
Release the underlying buffer exposed by the PickleBuffer object.
What can be pickled and unpickled?¶
The following types can be pickled:
None , True , and False ;
integers, floating-point numbers, complex numbers;
strings, bytes, bytearrays;
tuples, lists, sets, and dictionaries containing only picklable objects;
functions (built-in and user-defined) accessible from the top level of a module (using def , not lambda );
classes accessible from the top level of a module;
instances of such classes whose the result of calling __getstate__() is picklable (see section Pickling Class Instances for details).
Attempts to pickle unpicklable objects will raise the PicklingError exception; when this happens, an unspecified number of bytes may have already been written to the underlying file. Trying to pickle a highly recursive data structure may exceed the maximum recursion depth, a RecursionError will be raised in this case. You can carefully raise this limit with sys.setrecursionlimit() .
Note that functions (built-in and user-defined) are pickled by fully qualified name , not by value. 2 This means that only the function name is pickled, along with the name of the containing module and classes. Neither the function’s code, nor any of its function attributes are pickled. Thus the defining module must be importable in the unpickling environment, and the module must contain the named object, otherwise an exception will be raised. 3
Similarly, classes are pickled by fully qualified name, so the same restrictions in the unpickling environment apply. Note that none of the class’s code or data is pickled, so in the following example the class attribute attr is not restored in the unpickling environment:
These restrictions are why picklable functions and classes must be defined at the top level of a module.
Similarly, when class instances are pickled, their class’s code and data are not pickled along with them. Only the instance data are pickled. This is done on purpose, so you can fix bugs in a class or add methods to the class and still load objects that were created with an earlier version of the class. If you plan to have long-lived objects that will see many versions of a class, it may be worthwhile to put a version number in the objects so that suitable conversions can be made by the class’s __setstate__() method.
Pickling Class Instances¶
In this section, we describe the general mechanisms available to you to define, customize, and control how class instances are pickled and unpickled.
In most cases, no additional code is needed to make instances picklable. By default, pickle will retrieve the class and the attributes of an instance via introspection. When a class instance is unpickled, its __init__() method is usually not invoked. The default behaviour first creates an uninitialized instance and then restores the saved attributes. The following code shows an implementation of this behaviour:
Classes can alter the default behaviour by providing one or several special methods:
In protocols 2 and newer, classes that implements the __getnewargs_ex__() method can dictate the values passed to the __new__() method upon unpickling. The method must return a pair (args, kwargs) where args is a tuple of positional arguments and kwargs a dictionary of named arguments for constructing the object. Those will be passed to the __new__() method upon unpickling.
You should implement this method if the __new__() method of your class requires keyword-only arguments. Otherwise, it is recommended for compatibility to implement __getnewargs__() .
Changed in version 3.6: __getnewargs_ex__() is now used in protocols 2 and 3.
This method serves a similar purpose as __getnewargs_ex__() , but supports only positional arguments. It must return a tuple of arguments args which will be passed to the __new__() method upon unpickling.
Changed in version 3.6: Before Python 3.6, __getnewargs__() was called instead of __getnewargs_ex__() in protocols 2 and 3.
Classes can further influence how their instances are pickled by overriding the method __getstate__() . It is called and the returned object is pickled as the contents for the instance, instead of a default state. There are several cases:
For a class that has no instance __dict__ and no __slots__ , the default state is None .
For a class that has an instance __dict__ and no __slots__ , the default state is self.__dict__ .
For a class that has an instance __dict__ and __slots__ , the default state is a tuple consisting of two dictionaries: self.__dict__ , and a dictionary mapping slot names to slot values. Only slots that have a value are included in the latter.
For a class that has __slots__ and no instance __dict__ , the default state is a tuple whose first item is None and whose second item is a dictionary mapping slot names to slot values described in the previous bullet.
Changed in version 3.11: Added the default implementation of the __getstate__() method in the object class.
Upon unpickling, if the class defines __setstate__() , it is called with the unpickled state. In that case, there is no requirement for the state object to be a dictionary. Otherwise, the pickled state must be a dictionary and its items are assigned to the new instance’s dictionary.
If __getstate__() returns a false value, the __setstate__() method will not be called upon unpickling.
Refer to the section Handling Stateful Objects for more information about how to use the methods __getstate__() and __setstate__() .
At unpickling time, some methods like __getattr__() , __getattribute__() , or __setattr__() may be called upon the instance. In case those methods rely on some internal invariant being true, the type should implement __new__() to establish such an invariant, as __init__() is not called when unpickling an instance.
As we shall see, pickle does not use directly the methods described above. In fact, these methods are part of the copy protocol which implements the __reduce__() special method. The copy protocol provides a unified interface for retrieving the data necessary for pickling and copying objects. 4
Although powerful, implementing __reduce__() directly in your classes is error prone. For this reason, class designers should use the high-level interface (i.e., __getnewargs_ex__() , __getstate__() and __setstate__() ) whenever possible. We will show, however, cases where using __reduce__() is the only option or leads to more efficient pickling or both.
The interface is currently defined as follows. The __reduce__() method takes no argument and shall return either a string or preferably a tuple (the returned object is often referred to as the “reduce value”).
If a string is returned, the string should be interpreted as the name of a global variable. It should be the object’s local name relative to its module; the pickle module searches the module namespace to determine the object’s module. This behaviour is typically useful for singletons.
When a tuple is returned, it must be between two and six items long. Optional items can either be omitted, or None can be provided as their value. The semantics of each item are in order:
A callable object that will be called to create the initial version of the object.
A tuple of arguments for the callable object. An empty tuple must be given if the callable does not accept any argument.
Optionally, the object’s state, which will be passed to the object’s __setstate__() method as previously described. If the object has no such method then, the value must be a dictionary and it will be added to the object’s __dict__ attribute.
Optionally, an iterator (and not a sequence) yielding successive items. These items will be appended to the object either using obj.append(item) or, in batch, using obj.extend(list_of_items) . This is primarily used for list subclasses, but may be used by other classes as long as they have append() and extend() methods with the appropriate signature. (Whether append() or extend() is used depends on which pickle protocol version is used as well as the number of items to append, so both must be supported.)
Optionally, an iterator (not a sequence) yielding successive key-value pairs. These items will be stored to the object using obj[key] = value . This is primarily used for dictionary subclasses, but may be used by other classes as long as they implement __setitem__() .
Optionally, a callable with a (obj, state) signature. This callable allows the user to programmatically control the state-updating behavior of a specific object, instead of using obj ’s static __setstate__() method. If not None , this callable will have priority over obj ’s __setstate__() .
New in version 3.8: The optional sixth tuple item, (obj, state) , was added.
Alternatively, a __reduce_ex__() method may be defined. The only difference is this method should take a single integer argument, the protocol version. When defined, pickle will prefer it over the __reduce__() method. In addition, __reduce__() automatically becomes a synonym for the extended version. The main use for this method is to provide backwards-compatible reduce values for older Python releases.
Persistence of External Objects¶
For the benefit of object persistence, the pickle module supports the notion of a reference to an object outside the pickled data stream. Such objects are referenced by a persistent ID, which should be either a string of alphanumeric characters (for protocol 0) 5 or just an arbitrary object (for any newer protocol).
The resolution of such persistent IDs is not defined by the pickle module; it will delegate this resolution to the user-defined methods on the pickler and unpickler, persistent_id() and persistent_load() respectively.
To pickle objects that have an external persistent ID, the pickler must have a custom persistent_id() method that takes an object as an argument and returns either None or the persistent ID for that object. When None is returned, the pickler simply pickles the object as normal. When a persistent ID string is returned, the pickler will pickle that object, along with a marker so that the unpickler will recognize it as a persistent ID.
To unpickle external objects, the unpickler must have a custom persistent_load() method that takes a persistent ID object and returns the referenced object.
Here is a comprehensive example presenting how persistent ID can be used to pickle external objects by reference.
Dispatch Tables¶
If one wants to customize pickling of some classes without disturbing any other code which depends on pickling, then one can create a pickler with a private dispatch table.
The global dispatch table managed by the copyreg module is available as copyreg.dispatch_table . Therefore, one may choose to use a modified copy of copyreg.dispatch_table as a private dispatch table.
creates an instance of pickle.Pickler with a private dispatch table which handles the SomeClass class specially. Alternatively, the code
does the same but all instances of MyPickler will by default share the private dispatch table. On the other hand, the code
modifies the global dispatch table shared by all users of the copyreg module.
Handling Stateful Objects¶
Here’s an example that shows how to modify pickling behavior for a class. The TextReader class opens a text file, and returns the line number and line contents each time its readline() method is called. If a TextReader instance is pickled, all attributes except the file object member are saved. When the instance is unpickled, the file is reopened, and reading resumes from the last location. The __setstate__() and __getstate__() methods are used to implement this behavior.
A sample usage might be something like this:
Custom Reduction for Types, Functions, and Other Objects¶
New in version 3.8.
Sometimes, dispatch_table may not be flexible enough. In particular we may want to customize pickling based on another criterion than the object’s type, or we may want to customize the pickling of functions and classes.
For those cases, it is possible to subclass from the Pickler class and implement a reducer_override() method. This method can return an arbitrary reduction tuple (see __reduce__() ). It can alternatively return NotImplemented to fallback to the traditional behavior.
If both the dispatch_table and reducer_override() are defined, then reducer_override() method takes priority.
For performance reasons, reducer_override() may not be called for the following objects: None , True , False , and exact instances of int , float , bytes , str , dict , set , frozenset , list and tuple .
Here is a simple example where we allow pickling and reconstructing a given class:
Out-of-band Buffers¶
New in version 3.8.
In some contexts, the pickle module is used to transfer massive amounts of data. Therefore, it can be important to minimize the number of memory copies, to preserve performance and resource consumption. However, normal operation of the pickle module, as it transforms a graph-like structure of objects into a sequential stream of bytes, intrinsically involves copying data to and from the pickle stream.
This constraint can be eschewed if both the provider (the implementation of the object types to be transferred) and the consumer (the implementation of the communications system) support the out-of-band transfer facilities provided by pickle protocol 5 and higher.
Provider API¶
The large data objects to be pickled must implement a __reduce_ex__() method specialized for protocol 5 and higher, which returns a PickleBuffer instance (instead of e.g. a bytes object) for any large data.
A PickleBuffer object signals that the underlying buffer is eligible for out-of-band data transfer. Those objects remain compatible with normal usage of the pickle module. However, consumers can also opt-in to tell pickle that they will handle those buffers by themselves.
Consumer API¶
A communications system can enable custom handling of the PickleBuffer objects generated when serializing an object graph.
On the sending side, it needs to pass a buffer_callback argument to Pickler (or to the dump() or dumps() function), which will be called with each PickleBuffer generated while pickling the object graph. Buffers accumulated by the buffer_callback will not see their data copied into the pickle stream, only a cheap marker will be inserted.
On the receiving side, it needs to pass a buffers argument to Unpickler (or to the load() or loads() function), which is an iterable of the buffers which were passed to buffer_callback. That iterable should produce buffers in the same order as they were passed to buffer_callback. Those buffers will provide the data expected by the reconstructors of the objects whose pickling produced the original PickleBuffer objects.
Between the sending side and the receiving side, the communications system is free to implement its own transfer mechanism for out-of-band buffers. Potential optimizations include the use of shared memory or datatype-dependent compression.
Example¶
Here is a trivial example where we implement a bytearray subclass able to participate in out-of-band buffer pickling:
The reconstructor (the _reconstruct class method) returns the buffer’s providing object if it has the right type. This is an easy way to simulate zero-copy behaviour on this toy example.
On the consumer side, we can pickle those objects the usual way, which when unserialized will give us a copy of the original object:
But if we pass a buffer_callback and then give back the accumulated buffers when unserializing, we are able to get back the original object:
This example is limited by the fact that bytearray allocates its own memory: you cannot create a bytearray instance that is backed by another object’s memory. However, third-party datatypes such as NumPy arrays do not have this limitation, and allow use of zero-copy pickling (or making as few copies as possible) when transferring between distinct processes or systems.
PEP 574 – Pickle protocol 5 with out-of-band data
Restricting Globals¶
By default, unpickling will import any class or function that it finds in the pickle data. For many applications, this behaviour is unacceptable as it permits the unpickler to import and invoke arbitrary code. Just consider what this hand-crafted pickle data stream does when loaded:
In this example, the unpickler imports the os.system() function and then apply the string argument “echo hello world”. Although this example is inoffensive, it is not difficult to imagine one that could damage your system.
For this reason, you may want to control what gets unpickled by customizing Unpickler.find_class() . Unlike its name suggests, Unpickler.find_class() is called whenever a global (i.e., a class or a function) is requested. Thus it is possible to either completely forbid globals or restrict them to a safe subset.
Here is an example of an unpickler allowing only few safe classes from the builtins module to be loaded:
A sample usage of our unpickler working as intended:
As our examples shows, you have to be careful with what you allow to be unpickled. Therefore if security is a concern, you may want to consider alternatives such as the marshalling API in xmlrpc.client or third-party solutions.
Performance¶
Recent versions of the pickle protocol (from protocol 2 and upwards) feature efficient binary encodings for several common features and built-in types. Also, the pickle module has a transparent optimizer written in C.
Examples¶
For the simplest code, use the dump() and load() functions.
The following example reads the resulting pickled data.
Pickle interface constructor registration for extension types.
Tools for working with and analyzing pickled data.
Indexed databases of objects; uses pickle .
Shallow and deep object copying.
High-performance serialization of built-in types.
Don’t confuse this with the marshal module
This is why lambda functions cannot be pickled: all lambda functions share the same name: <lambda> .
The exception raised will likely be an ImportError or an AttributeError but it could be something else.
The copy module uses this protocol for shallow and deep copying operations.
The limitation on alphanumeric characters is due to the fact that persistent IDs in protocol 0 are delimited by the newline character. Therefore if any kind of newline characters occurs in persistent IDs, the resulting pickled data will become unreadable.
Лучшие способы сохранения данных в Python
Привет читатели! В этом руководстве мы будем иметь дело с тем, как мы можем эффективно сохранять данные в Python.
- Автор записи
Привет читатели! В этом руководстве мы будем иметь дело с тем, как мы можем эффективно сохранять данные в Python.
Как сохранить данные в Python?
Когда мы работаем над приложениями Python, мы будем иметь дело с объектами Python напрямую, так как все является объектом в Python. Давайте посмотрим на некоторые способы, которыми мы можем легко хранить их!
1. Использование сочинения для хранения объектов Python
Если мы хотим держать вещи простыми, мы можем использовать модуль сортировки, который является частью стандартной библиотеки для сохранения данных в Python.
Мы можем «сортировать» объекты Python в пиловый файл, который мы можем использовать для сохранения данных/загрузки данных.
Поэтому, если у вас есть пользовательский объект, который вам может потребоваться хранить/извлекать, вы можете использовать этот формат:
Если вы запускаете этот скрипт, вы заметите файл под названием data.pickle , который содержит сохраненные данные.
Чтобы снова загрузить один и тот же объект, мы могли бы использовать Pickle.Load () используя аналогичную логику.
Мы только что успешно восстановили наши старые данные!
2. Использование SQLite3 для постоянного сохранения данных в Python
Если вы хотите использовать постоянную базу данных для сохранения данных в Python, вы можете использовать SQLite3 Библиотека, которая предоставляет вам API для использования баз данных SQLite.
Опять же, это часть стандартной библиотеки, поэтому нет необходимости устанавливать ничего!
Однако, поскольку это реляционная база данных, вы не можете напрямую давить объекты Python, как в Парил Отказ
Вам придется сериализовать и десериализировать их в соответствующие типы баз данных.
Посмотрите на некоторые примеры, вы можете обратиться к этой статье на использовании SQLite в Python.
3. Использование SQLICENCT в качестве постоянного кэша
Если вы найдете использование SQLite3 Слишком утомительно, есть гораздо лучшее решение! Вы можете использовать sqlized Для хранения постоянных данных, и это внутренне использует SQLite3 база данных для обработки хранилища.
Вы должны установить этот пакет, используя PIP:
Единственное, что вы должны иметь в виду, это то, что вам нужно использовать Ключ: значение отображения для хранения/извлечения данных, как и словарь!
Вот очень простой пример, используя MyClass пример.
Действительно, мы просто загрузили наш объект Python! Если вы заметите, sqlized создаст базу данных cache.sqlite3 автоматически, если оно не существует, а затем используйте его для хранения/загрузки данных.
Заключение
В этой статье мы смотрели, как мы можем использовать Python для хранения данных по-разному.
Глава 13. Сериализация объектов Python
На первый взгляд, идея сериализации проста. У вас есть структура данных в памяти, которую вы хотите сохранить, использовать повторно, или кому-либо отправить. Как бы вы это сделали? Это зависит от того, как вы хотите ее сохранить, как вы хотите повторно ее использовать, и кому вы хотите ее отправить. Многие игры позволяют вам сохранять ваш прогресс перед выходом, а при следующем запуске возобновлять игру с того же места (на самом деле, многие неигровые приложения также позволяют это делать). В этом случае, структура, которая хранит ваш прогресс в игре, должна быть сохранена на диске, когда вы выходите из игры, и загружена с диска, когда вы ее снова запускаете. Данные предназначены только для использования той же программой, что и создала их, они никогда не посылаются по сети и никогда не читаются ничем, кроме создавшей их программы. Поэтому проблемы совместимости ограничены тем, чтобы более поздние версии программы могли читать данные, созданные ранними версиями.
Для таких случаев идеально подходит модуль pickle . Он входит в стандартную библиотеку Python и поэтому доступен всегда. Он быстр; большая его часть написана на C, как и сам интерпретатор Python. Он может сохранять произвольные сложные структуры данных Python.
Что может сохранять модуль pickle ?
- Все встроенные типы данных Python: логические значения, целые числа, числа с плавающей точкой, комплексные числа, строки, объекты bytes , массивы байтов и None .
- Списки, кортежи, словари и множества, содержащие любую комбинацию встроенных типов данных
- Списки, кортежи, словари и множества, содержащие любую комбинацию списков, кортежей, словарей и множеств, содержащих любую комбинацию встроенных типов данных (и так далее, вплоть до максимального уровня вложенности, который поддерживает Python).
- Функции, классы и экземпляры классов.
Если для вас этого недостаточно, то модуль pickle еще и расширяем. Если вас заинтересовала его расширяемость, то смотрите ссылки в разделе «Материалы для дальнейшего чтения» в конце главы.
13.1.1 Небольшое замечание о примерах в данной главе
Данная глава рассказывает об истории с двумя интерактивными оболочками (консолями) Python. Все примеры в этой главе – часть одной большей истории. По мере демонстрации работы с модулями pickle и json вам нужно будет переключаться назад и вперед между двумя оболочками Python.
Чтобы не запутаться, откройте интерактивную оболочку Python и определите следующую переменную:
Оставьте это окно открытым. Теперь откройте еще одну консоль Python и определите следующую переменную:
В данной главе я буду использовать переменную shell , чтобы показать, какую именно интерактивную оболочку Python я использую в каждом примере.
13.2 Сохранение данных в файл pickle
Модуль pickle работает со структурами данных. Давайте создадим одну.
- Строка 1. Всё дальнейшее происходит в консоли Python #1.
- Строка 3. Идея в том, чтобы создать словарь, который будет представлять что-нибудь полезное, например, элемент entry в рассылке Atom. Но, чтобы раскрыть возможности модуля pickle , я хочу убедиться, что он содержит несколько разных типов данных. Не вчитывайтесь слишком сильно в эти переменные.
- Строка 11. Модуль time содержит структуру данных ( struct_time ) для представления момента времени (с точностью до миллисекунд) и функции для работы с этими структурами. Функция strptime() принимает форматированную строку и преобразует ее в struct_time . Это строка в стандартном формате, но вы можете контролировать ее при помощи кодов форматирования. Для более подробного описания загляните в описание модуля time .
Теперь у нас есть симпатичный словарь Python. Давайте сохраним его в файл.
- Строка 1. Мы всё еще в первой консоли
- Строка 4. Чтобы открыть файл, используем функцию open() . Чтобы открыть файл для записи в двоичном режиме, установим режим работы с файлом в ' wb '. Обернем это всё в оператор with , чтобы быть уверенными, что, когда мы завершим с ним работу, файл закроется автоматически.
- Строка 5. Функция dump() модуля pickle принимает сериализуемую структуру данных Python, сериализует ее в двоичный, Python-зависимый формат, используя последнюю версию протокола pickle , и сохраняет ее в открытый файл.
Последнее предложение было очень важным.
- Модуль pickle принимает структуру данных Python и сохраняет ее в файл.
- Чтобы выполнить это, он сериализует структуру данных, используя формат данных под названием «протокол pickle».
- Протокол pickle зависит от Python; здесь нет гарантий совместимости с другими языками. Вы, возможно, не сможете взять только что созданный файл entry.pickle и сделать с ним что-либо полезное на Perl, PHP, Java или любом другом языке программирования.
- Модулем pickle может быть сериализована не любая структура данных Python. Протокол pickle менялся несколько раз с добавлением новых типов данных в язык Python, но у него всё еще есть ограничения.
- Как результат этих изменений, нет гарантии совместимости даже между разными версиями Python. Новые версии Python поддерживают старые форматы сериализации, но старые версии Python не поддерживают новые форматы (поскольку не поддерживают новые типы данных).
- Пока вы не укажете иное, функции модуля pickle будут использовать последнюю версию протокола pickle . Это сделано для уверенности в том, что вам будет предоставлена максимальная гибкость в типах данных, которые вы можете сериализовать, но это также значит, что полученный файл будет невозможно прочитать при помощи более старых версий Python, которые не поддерживают последнюю версию протокола pickle .
- Последняя версия протокола pickle – это двоичный формат. Убедитесь, что открываете файлы pickle в двоичном режиме, или данные при записи будут повреждены.
13.3 Загрузка данных из фала pickle
Теперь переключитесь во вторую консоль Python, т.е. не в ту, где вы создали словарь entry .
- Строка 1. Это вторая консоль Python.
- Строка 3. Здесь переменная entry не определена. Мы определили переменную entry в первой консоли Python, но это полностью другое окружение со своим собственным состоянием.
- Строка 8. Откроем файл entry.pickle , который мы создали в первой консоли Python. Модуль pickle использует двоичный формат данных, поэтому открывать pickle-файлы мы всегда должны в двоичном режиме.
- Строка 9. Функция pickle.load() принимает потоковый объект, читает сериализованные данные из потока, создает новый объект Python, восстанавливает сериализованные данные в этот новый объект Python, и возвращает этот новый объект Python.
- Строка 11. Теперь переменная entry – это словарь со знакомыми ключами и значениями.
Результат цикла pickle.dump() / pickle.load() – это новая структура данных эквивалентная исходной структуре данных.
- Строка 1. Переключаемся обратно в первую консоль Python.
- Строка 3. Открываем файл entry.pickle .
- Строка 4. Загружаем сериализованные данные в новую переменную entry2 .
- Строка 6. Python подтверждает, что эти два словаря( entry и entry2 ) равны. В этой консоли мы создали entry с нуля, начиная с пустого словаря и вручную присваивая значения ключам. Мы сериализовали этот словарь и сохранили в файле entry.pickle . Теперь мы прочитали сериализованные данные из этого файла и создали идеальную копию исходной структуры данных.
- Строка 8. Равенство не значит идентичность. Я сказал, что мы создали идеальную копию исходной структуры данных, и это правда. Но это всё же копия.
- Строка 10. По причинам, которые станут ясны позже в этой главе, я хочу указать, что значение ключа ' tags ' – это кортеж, и значение ' internal_id ' – это объект bytes .
13.4 Использование pickle без файлов
Пример из предыдущего раздела показал, как сериализовать объект Python непосредственно в файл на диске. Но что, если файл вам не нужен? Вы можете сериализовать в объект bytes в памяти.
- Строка 3. Функция pickle.dumps() (обратите внимание на 's' в конце имени функции) выполняет такую же сериализацию, как и функция pickle.dump() . Но вместо того, чтобы принимать потоковый объект и записывать сериализованные данные на диск, она просто возвращает эти сериализованные данные
- Строка 4. Поскольку протокол pickle использует двоичный формат данных, функция pickle.dumps() возвращает объект типа bytes .
- Строка 6. Функция pickle.loads() (снова обратите внимание на 's' в конце имени функции) выполняет такую же десериализацию, как и функция pickle.load() . Но вместо того, чтобы принимать потоковый объект и читать сериализованные данные из файла, она принимает объект типа bytes , содержащий сериализованные данные, такие, какие возвращает функция pickle.dumps() .
- Строка 7. Конечный результат такой же: идеальная копия исходного словаря.
13.5 Байты и строки снова поднимают свои уродливые головы
Протокол pickle существует уже много лет, и он развивался по мере развития самого Python'а. Сейчас существует четыре разных версии протокола pickle .
- В Python 1.x было две версии протокола pickle , текстовый формат (версия 0) и двоичный формат (версия 1).
- Python 2.3 добавил новый протокол pickle (версия 2), чтобы поддерживать новый функционал объектов классов Python. У него двоичный формат.
- Python 3.0 добавил еще один протокол pickle (версия 3) с полной поддержкой объектов типа bytes и байтовых массивов. У него так же двоичный формат.
О, смотрите, разница между строками и байтами снова поднимает свою уродливую голову (если вы удивлены, то были недостаточно внимательны). На практике это значит, что в то время, как Python 3 может читать данные сохраненные при помощи протокола версии 2, Python 2 не может читать данные сохраненные при помощи протокола версии 3.
13.6 Отладка файлов pickle
Как выглядит протокол pickle ? Давайте ненадолго отложим консоль Python и посмотрим, что внутри файла entry.pickle , который мы создали. Для не вооруженного взгляда он выглядит как тарабарщина.
Не слишком-то полезно. Вы можете видеть строки, но остальные типы данных выглядят как непечатаемые (или, по крайней мере, как нечитаемые) символы. Поля даже не разделены хотя бы табуляциями или пробелами. Это не тот формат, который вы бы захотели отлаживать вручную.
Самая интересная часть информации в этом листинге находится в последней строке потому, что она в себя включает версию протокола pickle , при помощи которого данный файл был сохранен. Явного маркера протокола pickle не существует. Чтобы определить, какую версию протокола использовали для сохранения pickle-файла, вам необходимо посмотреть на маркеры(" opcodes ") внутри сохраненных данных и использовать жестко заданную информацию, какие маркеры добавлялись с каждой версией протокола pickle . Функция pickletools.dis() делает именно это, и она выводит в печать результат в последней строке листинга. Ниже показана функция, которая возвращает только номер версии, без вывода данных.
А вот она же в действии:
13.7 Сериализация объектов Python для чтения при помощи других языков программирования
Формат данных, используемый модулем pickle , является Python-зависимым. Он не пытается быть совместимым с другими языками программирования. Если среди ваших требований есть совместимость с другими языками программирования, то вам следует присмотреться к другим форматам сериализации. Один из таких форматов – JSON. «JSON» – это аббревиатура от «JavaScript Object Notation», но не позволяйте названию ввести вас в заблуждение – JSON определенно был разработан для использования несколькими языками программирования.
Python 3 включает в стандартную библиотеку модуль json . Как и модуль pickle , модуль json имеет функции для сериализации структур данных, сохранения сериализованных данных на диск, загрузки сериализованных данных с диска и десереализации данных обратно в новый объект Python. Но есть несколько важных отличий. Во-первых, формат данных JSON – текстовый, а не двоичный. RFC 4627 определяет формат JSON и то, как различные типы данных должны быть преобразованы в текст. Например, логическое значение сохраняется как пятисимвольная строка ' false ' или четырехсимвольная строка ' true '. Все значения в JSON регистрозависимые.
Во-вторых, как и с любым текстовым форматом, существует проблема пробельных символов. JSON позволяет вставлять между значениями произвольное количество пробельных символов (табуляций, символов возврата каретки и перевода строки). Пробельные символы в нем «незначащие», что означает, что кодировщики JSON могут добавлять так много или так мало пробельных символов, как захотят, а декодировщики JSON будут игнорировать эти пробельные символы между значениями. Это позволяет нам использовать красивую печать (pretty-print) для вывода данных в формате JSON, красиво печатая вложенные значения с различными размерами отступов, чтобы всё это было можно читать даже в текстовом редакторе. У модуля json в Python имеется такая опция красивой печати при кодировании данных.
В-третьих, существует многолетняя проблема с кодировкой символов. JSON кодирует значения как обычный текст, но, как вы знаете, такого понятия как «обычный текст» не существует. JSON должен сохраняться в кодировке Unicode (UTF-32, UTF-16, или, по умолчанию, UTF-8), а раздел 3 из RFC 4627 определяет то, как указать используемую кодировку.
13.8 Сохранение данных в файл JSON
JSON выглядит удивительно похоже на структуру данных, которую вы могли бы определить вручную в JavaScript. Это не случайно, вы действительно можете использовать функцию eval() из JavaScript чтобы «декодировать» сериализованные в json данные (обычные предостережения ненадежных входных данных принимаются, но дело в том, что JSON это корректный JavaScript). По сути, JSON может быть уже хорошо знаком вам.
- Строка 3. Вместо того чтобы использовать уже имеющуюся структуру данных entry , мы собираемся создать новую структуру. Позже в этой главе мы увидим, что случится, когда мы попробуем кодировать в JSON более сложную структуру данных.
- Строка 10. JSON – это текстовый формат, что означает, что мы должны открыть файл в текстовом режиме и указать кодировку. Вы никогда не ошибетесь, используя UTF-8.
- Строка 11. Как и модуль pickle , модуль json определяет функцию dump() , которая принимает структуру данных Python и записываемый потоковый объект. Функция dump() сериализует структуру данных Python и записывает ее в этот потоковый объект. Выполняя это в операторе with , мы можем быть уверены, что, когда мы завершим работу с файлом, он будет корректно закрыт.
Ну и как выглядит результат сериализации в формате JSON?
Это определенно намного более читаемо, чем файл pickle. Но JSON между значениями может содержать произвольное количество пробельных символов, и модуль json предоставляет простой способ для создания еще более читаемого файла JSON.
- Строка 4. Если вы передадите функции json.dump() параметр indent , это сделает конечный файл JSON более читаемым, увеличив при этом размер файла. Параметр indent – это целое число. 0 означает, «расположить каждое значение на отдельной строке». Число больше нуля означает, «расположить каждое значение на отдельной строке, и использовать это количество пробелов для отступов во вложенных структурах данных».
Результат будет следующим:
13.9 Соответствие типов данных Python в JSON
Поскольку JSON разрабатывался не только для Python, то в покрытии типов данных Python есть некоторые недочеты. Некоторые из них – это просто отличия в названиях типов, но есть два важных типа данных Python, которые полностью упущены из виду. Посмотрим, сможете ли вы заметить их:
| Примечания | JSON | Python 3 |
|---|---|---|
| object | dictionary | |
| array | list | |
| string | string | |
| integer | integer | |
| real number | float | |
| * | true | True |
| * | false | False |
| * | null | None |
| * Все значения JSON чувствительны к регистру . | ||
Заметили что пропущено? Кортежи и байты! В JSON есть тип массив, который модуль json ставит в соответствие типу список в Python, но там нет отдельного типа для «статичных массивов» (кортежей). В JSON есть хорошая поддержка строк, но нет поддержки объектов типа bytes или массивов байт.
13.10 Сериализация типов данных, не поддерживаемых JSON
Даже если в JSON нет встроенной поддержки байтов, не значит, что вы не можете сериализовать объекты типа bytes . Модуль json предоставляет расширяющие хуки для кодирования и декодирования неизвестных типов данных. (Под «неизвестными» я имел в виду «не определенные в JSON». Очевидно, что модуль json знает о массивах байт, но он ограничен спецификации JSON.) Если вы хотите закодировать bytes или другие типы данных, которые JSON по умолчанию не поддерживает, вам необходимо предоставить специальные кодировщики и декодировщики для этих типов данных.
- Строка 3. Хорошо, настало время вернуться к структуре данных entry . Там есть всё: логическое значение, значение None , строка, кортеж строк, объект bytes и структура time .
- Строка 12. Я знаю, что говорил это ранее, но повторюсь еще раз: JSON – это текстовый формат. Открывайте файлы JSON всегда в текстовом режиме с кодировкой UTF-8.
- Строка 13. Что ж, это не хорошо. Что произошло?
А вот что: функция json.dump() попробовала сериализовать объект bytes b'\xDE\xD5\xB4\xF8' , но ей это не удалось, потому что в JSON нет поддержки объектов bytes . Однако, если сохранение таких объектов важно для вас, вы можете определить свой "миниформат сериализации".
- Строка 1. Чтобы определить собственный "миниформат сериализации" для типов данных, не поддерживаемых JSON по умолчанию, просто определите функцию, которая принимает объект Python как параметр. Этот объект Python будет именно тем объектом, который функция json.dump() не сможет сериализовать сама – в данном случае, это объект bytes b'\xDE\xD5\xB4\xF8' .
- Строка 2. Ваша специальная функция сериализации должна проверить тип объекта Python, который ей передала функция json.dump() . Это не строго обязательно, если ваша функция сериализует только один тип данных, но это делает понятным, какой случай покрывает данная функция, и делает более простым расширение, если позже вам понадобится сериализовать больше типов данных.
- Строка 4. В данном случае я решил конвертировать объект bytes в словарь. Ключ __class__ будет содержать название исходного типа данных (как строку, ' bytes '), а ключ __value__ будет хранить само значение. Конечно, это не может быть объект типа bytes , поэтому нужно преобразовать его во что-то сериализуемое в JSON. Объект bytes – это просто последовательность целых чисел, где каждое число находится в диапазоне от 0 до 255. Мы можем использовать функцию list() , чтобы преобразовать объект bytes в список целых чисел. Итак, b'\xDE\xD5\xB4\xF8' становится [222, 213, 180, 248] . (Проверьте! Это работает! Байт \xDE в шестнадцатеричной системе – это 222 в десятичной, \xD5 – это 213 , и так далее.)
- Строка 5. Это строка важна. Структура данных, которую вы сериализуете может содержать типы данных, которые не могут обработать ни встроенный сериализатор JSON, ни ваш специальный сериализатор. В этом случае, ваш обработчик должен выбросить исключение TypeError , чтобы функция json.dump() узнала, что ваш сериализатор не смог распознать тип данных.
Всё, делать больше ничего не нужно. В действительности, эта специальная функция сериализации возвращает словарь Python, а не строку. Вы не пишите сериализацию в JSON полностью сами, вы просто выполняете преобразование в поддерживаемый тип данных. Функция json.dump() сделает всё остальное.
- Строка 3. Модуль customserializer – это то, где мы только что определили функцию to_json() в предыдущем примере.
- Строка 4. Текстовый режим, UTF-8, бла-бла-бла. (Вы забудете! Я сам иногда забываю! И всё работает замечательно, пока в один момент не сломается, и тогда оно начинает ломаться еще эффектнее.)
- Строка 5. Это важный фрагмент: чтобы встроить нашу специальную функцию преобразования в функцию json.dump() , передаем нашу функцию в json.dump() в параметре default . (Ура, в Python всё является объектом!)
- Строка 19. Хорошо, это действительно работает. Но посмотрите на исключение. Теперь функция json.dump() больше не жалуется, что не может сериализовать объект bytes . Теперь она жалуется на совершенно другой объект: time.struct_time .
Хоть получение другого исключения и не выглядит как прогресс, но на самом деле это он! Нужно просто внести еще одну корректировку, чтобы обрабатывался и этот тип.
- Строка 4. Добавляя обработку в нашу существующую функцию customserializer.to_json() , мы должны проверить, является ли объект Python (с которым у функции json.dump() проблемы) объектом time.struct_time .
- Строка 6. Если это так, то выполняем нечто, похожее на преобразование, которое мы выполняли с объектом bytes : преобразуем объект time.struct_time в словарь, который содержит только сериализуемые в JSON типы данных. В данном случае, простейший способ преобразовать дату/время в сериализуемое в JSON значение – это преобразовать ее в строку с помощью функции time.asctime() . Функция time.asctime() преобразует ужасно выглядящую структуру time.struct_time в строку ' Fri Mar 27 22:20:42 2009 '.
С этими двумя специальными преобразованиями, вся структура данных entry должна сериализоваться в JSON без каких-либо проблем.
13.11 Загрузка данных из файла JSON
Как и в модуле pickle , в модуле json есть функция load() , которая принимает потоковый объект, читает из него данные в формате JSON и создает новый объект Python, который будет копией структуры данных в JSON.
- Строка 3. Для демонстрации переключаемся на вторую консоль Python и удаляем структуру данных entry , которую создали ранее в этой главе при помощи модуля pickle .
- Строка 10. В простейшем случае, функция json.load() работает так же, как и функция pickle.load() . Передаем ей потоковый объект, а она возвращает новый объект Python.
- Строка 12. У меня хорошие и плохие новости. Сначала хорошие: функция json.load() успешно прочитала файл entry.json , который мы создали в первой консоли Python, и создала новый объект Python, который содержит данные. А теперь плохие: она не воссоздала исходную структуру данных entry . Два значения (' internal_id ' и ' published_date ') были созданы как словари – а именно, как словари со значениями, совместимыми с JSON (именно их мы создали в функции преобразования to_json() ).
Функция json.load() ничего не знает ни о какой функции преобразования, которую мы могли передать в json.dump() . Теперь нам нужно создать функцию, обратную to_json() , – функцию, которая примет специально преобразованный объект JSON и преобразует его обратно в исходный тип данных Python.
- Строка 2. Эта функция преобразования так же принимает один параметр и возвращает одно значение. Но параметр, который она принимает, – не строка, а объект Python – результат десереализации строки JSON в объект Python.
- Строка 3. Всё, что вам нужно, – это проверить, содержит ли данный объект ключ ' __class__ ', который был создан функцией to_json() . Если да, то значение этого ключа ' __class__ ', скажет нам, как декодировать это значение обратно в исходный тип данных Python.
- Строка 5. Чтобы декодировать строку времени, возвращаемую функцией time.asctime() , мы используем функцию time.strptime() . Эта функция принимает форматированную строку даты/времени (в определяемом формате, но по умолчанию этот формат совпадает с форматом по умолчанию функции time.asctime() ) и возвращает time.struct_time .
- Строка 7. Для преобразования списка целых чисел обратно в объект bytes мы можем использовать функцию bytes() .
Вот и всё. Всего два типа данных обрабатывались функцией to_json() , и теперь эти типы данных обрабатываются функцией from_json() . Ниже показан результат:
- Строка 5. Чтобы добавить функцию from_json() в процесс десериализации, передаем ее функции json.load() в параметре object_hook . Функции, которые принимают функции; как удобно!
- Строка 7. Структура данных entry теперь содержит ключ ' internal_id ' со значением типа bytes . Также она содержит ключ ' published_date ' со значением типа time.struct_time .
Хотя остался еще один глюк.
- Строка 7. Даже после встраивания функции to_json() в сериализацию и функции from_json() в десериализацию у нас всё еще нет идеальной копии исходной структуры данных. Почему?
- Строка 9. В исходной структуре данных entry значение по ключу ' tags ' было кортежем из трех строк.
- Строка 11. Но в воссозданной структуре данных entry2 значение по ключу ' tags ' – это список из трех строк. JSON не видит разницы между кортежами и списками; в нем есть только один похожий на список тип данных, массив, и модуль json при сериализации тихо преобразует и списки, и кортежи в массивы JSON. В большинстве случаев, вы можете игнорировать разницу между кортежами и списками, но об этом нужно помнить, когда работаете с модулем json .
13.12 Материалы для дальнейшего чтения
Много статей о модуле pickle ссылаются на cPickle. В Python 2 существует две реализации модуля pickle : одна написана на чистом Python, другая на C (но всё еще вызываемая из Python). В Python 3 эти два модуля были объединены, поэтому всегда следует использовать просто import pickle . Вы можете найти эти статьи полезными, но игнорируйте устаревшую информацию о cPickle.