[Zope3-checkins] CVS: Zope3/src/datetime - _datetime.py:1.30

Tim Peters tim.one@comcast.net
Thu, 30 Jan 2003 21:25:06 -0500


Update of /cvs-repository/Zope3/src/datetime
In directory cvs.zope.org:/tmp/cvs-serv15264/src/datetime

Modified Files:
	_datetime.py 
Log Message:
New pickling scheme, so that pickles produced by datetime.py and by
Python 2.3 datetimemodule.c are identical.  This is bought at the cost
of a tiny type insecurity:  in addition to the normal collection of
constructor arguments, __new__ also accepts a state string now.  This
means the type object can be returned directly as "the callable" by
__reduce__, and "the argument" can be a compact string (a copy of
the object memory, in the C implementation; the motivation isn't
really clear in this pure-Python implementation).

This also allows normal paths to be taken, allowing to remove all the
copy_reg() initialization hair, and layers of artificial pickler and
unpickler functions.  Thank Guido for the design!

I think it's still the case that tzinfo subclasses need to supply an
__init__ that's callable with no arguments (unless they arrange for
their own pickling).


=== Zope3/src/datetime/_datetime.py 1.29 => 1.30 ===
--- Zope3/src/datetime/_datetime.py:1.29	Fri Jan 24 14:05:23 2003
+++ Zope3/src/datetime/_datetime.py	Thu Jan 30 21:24:32 2003
@@ -402,9 +402,9 @@
     felt like it.
     """
 
-    def __init__(self, days=0, seconds=0, microseconds=0,
-                 # XXX The following should only be used as keyword args:
-                 milliseconds=0, minutes=0, hours=0, weeks=0):
+    def __new__(cls, days=0, seconds=0, microseconds=0,
+                # XXX The following should only be used as keyword args:
+                milliseconds=0, minutes=0, hours=0, weeks=0):
         # Doing this efficiently and accurately in C is going to be difficult
         # and error-prone, due to ubiquitous overflow possibilities, and that
         # C double doesn't have enough bits of precision to represent
@@ -505,12 +505,17 @@
         assert isinstance(d, (int, long))
         assert isinstance(s, int) and 0 <= s < 24*3600
         assert isinstance(us, int) and 0 <= us < 1000000
+
+        self = object.__new__(cls)
+
         self.__days = d
         self.__seconds = s
         self.__microseconds = us
         if abs(d) > 999999999:
             raise OverflowError("timedelta # of days is too large: %d" % d)
 
+        return self
+
     def __repr__(self):
         if self.__microseconds:
             return "%s(%d, %d, %d)" % ('datetime.' + self.__class__.__name__,
@@ -606,12 +611,8 @@
                 self.__microseconds != 0)
 
     # Pickle support.
-    # This magic class attr is necessary for pickle compatibility with the
-    # C implementation.
-    __safe_for_unpickling__ = True
 
-    def __reduce__(self):
-        return type(self), (), self.__getstate__()
+    __safe_for_unpickling__ = True      # For Python 2.2
 
     def __getstate__(self):
         return (self.__days, self.__seconds, self.__microseconds)
@@ -619,6 +620,9 @@
     def __setstate__(self, tup):
         self.__days, self.__seconds, self.__microseconds = tup
 
+    def __reduce__(self):
+        return (self.__class__, self.__getstate__())
+
 timedelta.min = timedelta(-999999999)
 timedelta.max = timedelta(days=999999999, hours=23, minutes=59, seconds=59,
                           microseconds=999999)
@@ -629,7 +633,7 @@
 
     Constructors:
 
-    __init__()
+    __new__()
     fromtimestamp()
     today()
     fromordinal()
@@ -653,17 +657,24 @@
     year, month, day
     """
 
-    def __init__(self, year, month, day):
+    def __new__(cls, year, month=None, day=None):
         """Constructor.
 
         Arguments:
 
         year, month, day (required, base 1)
         """
+        if isinstance(year, str):
+            # Pickle support
+            self = object.__new__(cls)
+            self.__setstate__((year,))
+            return self
         _check_date_fields(year, month, day)
+        self = object.__new__(cls)
         self.__year = year
         self.__month = month
         self.__day = day
+        return self
 
     # Additional constructors
 
@@ -842,15 +853,22 @@
 
     # Pickle support.
 
+    __safe_for_unpickling__ = True      # For Python 2.2
+
     def __getstate__(self):
         yhi, ylo = divmod(self.__year, 256)
-        return "%c%c%c%c" % (yhi, ylo, self.__month, self.__day)
+        return ("%c%c%c%c" % (yhi, ylo, self.__month, self.__day), )
 
-    def __setstate__(self, string):
+    def __setstate__(self, t):
+        assert isinstance(t, tuple) and len(t) == 1, `t`
+        string = t[0]
         assert len(string) == 4
         yhi, ylo, self.__month, self.__day = map(ord, string)
         self.__year = yhi * 256 + ylo
 
+    def __reduce__(self):
+        return (self.__class__, self.__getstate__())
+
 _date_class = date  # so functions w/ args named "date" can get at the class
 
 date.min = date(1, 1, 1)
@@ -909,19 +927,34 @@
         else:
             return dt
 
-    # pickle support
+    # Pickle support.
 
-    __safe_for_unpickling__ = True
+    __safe_for_unpickling__ = True      # For Python 2.2
 
     def __reduce__(self):
-        return type(self), (), self.__dict__
+        getinitargs = getattr(self, "__getinitargs__", None)
+        if getinitargs:
+            args = getinitargs()
+        else:
+            args = ()
+        getstate = getattr(self, "__getstate__", None)
+        if getstate:
+            state = getstate()
+        else:
+            state = getattr(self, "__dict__", None) or None
+        if state is None:
+            return (self.__class__, args)
+        else:
+            return (self.__class__, args, state)
+
+_tzinfo_class = tzinfo   # so functions w/ args named "tinfo" can get at it
 
 class time(object):
     """Time with time zone.
 
     Constructors:
 
-    __init__()
+    __new__()
 
     Operators:
 
@@ -940,7 +973,7 @@
     hour, minute, second, microsecond, tzinfo
     """
 
-    def __init__(self, hour=0, minute=0, second=0, microsecond=0, tzinfo=None):
+    def __new__(cls, hour=0, minute=0, second=0, microsecond=0, tzinfo=None):
         """Constructor.
 
         Arguments:
@@ -949,6 +982,11 @@
         second, microsecond (default to zero)
         tzinfo (default to None)
         """
+        self = object.__new__(cls)
+        if isinstance(hour, str):
+            # Pickle support
+            self.__setstate__((hour, minute or None))
+            return self
         _check_tzinfo_arg(tzinfo)
         _check_time_fields(hour, minute, second, microsecond)
         self.__hour = hour
@@ -956,6 +994,7 @@
         self.__second = second
         self.__microsecond = microsecond
         self._tzinfo = tzinfo
+        return self
 
     # Read-only field accessors
     hour = property(lambda self: self.__hour, doc="hour (0-23)")
@@ -1138,6 +1177,8 @@
 
     # Pickle support.
 
+    __safe_for_unpickling__ = True      # For Python 2.2
+
     def __getstate__(self):
         us2, us3 = divmod(self.__microsecond, 256)
         us1, us2 = divmod(us2, 256)
@@ -1164,6 +1205,9 @@
         else:
             self._tzinfo = state[1]
 
+    def __reduce__(self):
+        return (self.__class__, self.__getstate__())
+
 _time_class = time  # so functions w/ args named "time" can get at the class
 
 time.min = time(0, 0, 0)
@@ -1175,11 +1219,16 @@
     # XXX needs docstrings
     # See http://www.zope.org/Members/fdrake/DateTimeWiki/TimeZoneInfo
 
-    def __init__(self, year, month, day, hour=0, minute=0, second=0,
-                 microsecond=0, tzinfo=None):
+    def __new__(cls, year, month=None, day=None, hour=0, minute=0, second=0,
+                microsecond=0, tzinfo=None):
+        if isinstance(year, str):
+            # Pickle support
+            self = date.__new__(cls, 1, 1, 1)
+            self.__setstate__((year, month))
+            return self
         _check_tzinfo_arg(tzinfo)
         _check_time_fields(hour, minute, second, microsecond)
-        date.__init__(self, year, month, day)
+        self = date.__new__(cls, year, month, day)
         # XXX This duplicates __year, __month, __day for convenience :-(
         self.__year = year
         self.__month = month
@@ -1189,6 +1238,7 @@
         self.__second = second
         self.__microsecond = microsecond
         self._tzinfo = tzinfo
+        return self
 
     # Read-only field accessors
     hour = property(lambda self: self.__hour, doc="hour (0-23)")
@@ -1521,6 +1571,10 @@
         seconds = self.hour * 3600 + (self.minute - tzoff) * 60 + self.second
         return hash(timedelta(days, seconds, self.microsecond))
 
+    # Pickle support.
+
+    __safe_for_unpickling__ = True      # For Python 2.2
+
     def __getstate__(self):
         yhi, ylo = divmod(self.__year, 256)
         us2, us3 = divmod(self.__microsecond, 256)
@@ -1550,6 +1604,9 @@
         else:
             self._tzinfo = state[1]
 
+    def __reduce__(self):
+        return (self.__class__, self.__getstate__())
+
 
 datetime.min = datetime(1, 1, 1)
 datetime.max = datetime(9999, 12, 31, 23, 59, 59, 999999)
@@ -1566,54 +1623,6 @@
     if firstweekday > THURSDAY:
         week1monday += 7
     return week1monday
-
-# Pickle support.  __getstate__ and __setstate__ work fine on their own,
-# but only because the classes here are implemented in Python.  The C
-# implementation had to get much trickier, and the code following emulates
-# what the C code had to do, so that pickles produced by the Python
-# implementation can be read by the C implementation, and vice versa.
-
-def _date_pickler(date):
-    state = date.__getstate__()
-    return _date_unpickler, (state,)
-
-def _date_unpickler(state):
-    self = date(1, 1, 1)
-    self.__setstate__(state)
-    return self
-
-def _tzinfo_pickler(tz):
-    return _tzinfo_unpickler, ()
-
-def _tzinfo_unpickler():
-    self = tzinfo()
-    return self
-
-def _time_pickler(tz):
-    state = tz.__getstate__()
-    return _time_unpickler, (state,)
-
-def _time_unpickler(state):
-    self = time()
-    self.__setstate__(state)
-    return self
-
-def _datetime_pickler(dtz):
-    state = dtz.__getstate__()
-    return _datetime_unpickler, (state,)
-
-def _datetime_unpickler(state):
-    self = datetime(1, 1, 1)
-    self.__setstate__(state)
-    return self
-
-# Register pickle/unpickle functions.
-from copy_reg import pickle
-pickle(date, _date_pickler, _date_unpickler)
-pickle(tzinfo, _tzinfo_pickler, _tzinfo_unpickler)
-pickle(time, _time_pickler, _time_unpickler)
-pickle(datetime, _datetime_pickler, _datetime_unpickler)
-del pickle
 
 """
 Some time zone algebra.  For a datetime x, let