[pickle] Different version of a class

Tim Peters tim_one at email.msn.com
Sun Jun 6 19:50:01 CEST 1999


[mlauer at asmoday-atm.rz.uni-frankfurt.de]
> my application uses pickle to store and load data.
>
> During developing cycle, many variables are added
> to the classes which are pickled. Whenever I load
> a file pickled with a previous version, some
> variables are not existent (naturally, because
> __init__ is not called). Is there an easy way
> to initialize the "new" variables or must I
> call __init__ - which would overwrite some of
> my saved variables.

As the 1.5.2 pickle docs hint,

    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.

For example, say you start with this class:

class Point:
    version = 1

    def __init__(self, x, y):
        print "in init w/ x, y", x, y
        self.x, self.y = x, y

    def __getstate__(self):
        return (Point.version, self.__dict__)

    def __setstate__(self, (version, dict)):
        if version == 1:
            self.__dict__ = dict
        else:
            raise ValueError("I'm only version %d of Point -- "
                             "can't set state from version %d" %
                             (Point.version, version))

When an instance is pickled, __getstate__ tells it to save away a 2-tuple,
consisting of the class version number and the instance dict.  On
unpickling, this 2-tuple is passed to __setstate__, which can do whatever it
wants.  In this case it's trivial because there is only one version of the
class, but at least version 1 of the class can detect it doesn't know how to
restore data from later versions!

Version 2 of the class may be:

class Point:
    version = 2

    def __init__(self, x, y):
        print "in init w/ x, y", x, y
        self.x, self.y = x, y
        import math
        self.dist = math.sqrt(x**2 + y**2)

    def __getstate__(self):
        return (Point.version, self.__dict__)

    def __setstate__(self, (version, dict)):
        if version == 1:
            self.__dict__ = dict
            import math
            self.dist = math.sqrt(self.x**2 + self.y**2)
        elif version == 2:
            self.__dict__ = dict
        else:
            raise ValueError("I'm only version %d of Point -- "
                             "can't set state from version %d" %
                             (Point.version, version))

That is, __getstate__ doesn't change, but Point.version gets bumped and
__setstate__  looks at the version number in effect at the time the pickle
was made.  Unpickling a version 1 instance now sprouts a dist attr by magic.

Note:  despite the simple-minded <wink> appeal of version numbers, in Python
I find it's almost always better to use hasattr or try/except; that is, to
probe the actual *behavior* rather than rely on always-poorly-documented
"version numbers".  So I'd be more likely to write these toy classes as:

class Point:
    version = 1

    def __init__(self, x, y):
        print "in init w/ x, y", x, y
        self.x, self.y = x, y

and later:

class Point:
    version = 2

    def __init__(self, x, y):
        print "in init w/ x, y", x, y
        self.x, self.y = x, y
        import math
        self.dist = math.sqrt(x**2 + y**2)

    def __setstate__(self, dict):
        self.__dict__ = dict
        if not hasattr(self, 'dist'):
            import math
            self.dist = math.sqrt(self.x**2 + self.y**2)

convolute-to-taste-ly y'rs  - tim






More information about the Python-list mailing list