Search code examples
pythontimepickle

Cannot unpickle an instance of a class which inherits from `time`


Consider the following class, which inherits from datetime.time:

class TimeWithAddSub(time):
    _date_base = date(2000, 1, 1)

    def __new__(cls, hour=0, minute=0, second=0, microsecond=0, tzinfo=None):
        obj = super(TimeWithAddSub, cls).__new__(cls, hour, minute, second, microsecond, tzinfo)
        obj._as_datetime = datetime(2000, 1, 1, hour, minute, second, microsecond, tzinfo=tzinfo)
        return obj

Trying to unpickle it gives the following error:

>>> t = TimeWithAddSub(10, 11, 12)
>>> b = pickle.dumps(t)
>>>    b'\x80\x04\x95T\x00\x00\x00\x00\x....' 
>>> t2 = pickle.loads(b) 
>>>    ... in __new__ ... TypeError: 'bytes' object cannot be interpreted as an integer

After some digging, it seems that the __new__() function in the unpickled object does not get the correct parameters:

  • hour is set to b'\x0b\x0c\r\x00\x00\x00'
  • the rest of the parameters get their default values (minute=0, second=0, microsecond=0, tzinfo=None)

I've tried overriding __reduce__() and then __getnewargs__(), but both approaches result in the same error.

Any ideas how to fix this?


Solution

  • The problem was that datetime.time implements __reduce_ex__(), which takes precedence over __reduce__().

    Once I implemented TimeWithAddSub.__reduce_ex__(), overriding the implementation in the base class, the problem was resolved:

    class TimeWithAddSub(time):
        _date_base = date(2000, 1, 1)
    
        def __new__(cls, hour=0, minute=0, second=0, microsecond=0, tzinfo=None):
            obj = super(TimeWithAddSub, cls).__new__(cls, hour, minute, second, microsecond, tzinfo)
            obj._as_datetime = datetime(2000, 1, 1, hour, minute, second, microsecond, tzinfo=tzinfo)
            return obj
    
        def __reduce_ex__(self, protocol):            
            return type(self), (self.hour, self.minute, self.second, self.microsecond, self.tzinfo)