[Zope-Checkins] CVS: Zope/lib/python/ExtensionClass - _ExtensionClass.c:1.1.2.5 tests.py:1.1.2.4

Jim Fulton cvs-admin at zope.org
Mon Nov 3 10:59:58 EST 2003


Update of /cvs-repository/Zope/lib/python/ExtensionClass
In directory cvs.zope.org:/tmp/cvs-serv16312/lib/python/ExtensionClass

Modified Files:
      Tag: zodb33-devel-branch
	_ExtensionClass.c tests.py 
Log Message:
Implemented custom mro hook for the ExtensionClass meta-class to
provide backward compatability.

Added additional pickling feature:
_v_ and _p_ variables are omitted from state.


=== Zope/lib/python/ExtensionClass/_ExtensionClass.c 1.1.2.4 => 1.1.2.5 ===
--- Zope/lib/python/ExtensionClass/_ExtensionClass.c:1.1.2.4	Wed Oct 29 06:09:17 2003
+++ Zope/lib/python/ExtensionClass/_ExtensionClass.c	Mon Nov  3 10:59:58 2003
@@ -22,8 +22,7 @@
 #define EC PyTypeObject
 
 static PyObject *str__of__, *str__get__, *str__class_init__, *str__init__;
-static PyObject *str__slotnames__, *copy_reg_slotnames, *__newobj__;
-static PyObject *str__getnewargs__, *str__getstate__, *str__new__;
+static PyObject *str__bases__, *str__mro__, *str__new__;
 
 #define OBJECT(O) ((PyObject *)(O))
 #define TYPE(O) ((PyTypeObject *)(O))
@@ -173,205 +172,10 @@
 	return res;
 }
 
-/* It's a dang shame we can't inherit __get/setstate__ from object :( */
-
-static PyObject *
-Base_slotnames(PyTypeObject *cls)
-{
-  PyObject *slotnames;
-
-  slotnames = PyDict_GetItem(cls->tp_dict, str__slotnames__);
-  if (slotnames != NULL) 
-    {
-      Py_INCREF(slotnames);
-      return slotnames;
-    }
-
-  slotnames = PyObject_CallFunctionObjArgs(copy_reg_slotnames, OBJECT(cls), 
-                                           NULL);
-  if (slotnames != NULL &&
-      slotnames != Py_None &&
-      ! PyList_Check(slotnames))
-    {
-      PyErr_SetString(PyExc_TypeError,
-                      "copy_reg._slotnames didn't return a list or None");
-      Py_DECREF(slotnames);
-      slotnames = NULL;
-    }
-  
-  return slotnames;
-}
-
-static PyObject *
-getdict(PyObject *self)
-{
-  PyObject **dict;
-
-  dict = _PyObject_GetDictPtr(self);
-  if (dict)
-    return *dict;
-  return NULL;
-}
-
-
-static PyObject *
-Base___getstate__(PyObject *self)
-{
-  PyObject *slotnames=NULL, *slots=NULL, *state=NULL;
-  int n=0;
-
-  slotnames = Base_slotnames(self->ob_type);
-  if (slotnames == NULL)
-    return NULL;
-
-  state = getdict(self);
-  if (state == NULL) 
-    state = Py_None;
-  Py_INCREF(state);
-
-  if (slotnames != Py_None)
-    {
-      int i;
-
-      slots = PyDict_New();
-      if (slots == NULL)
-        goto end;
-
-      for (i = 0; i < PyList_GET_SIZE(slotnames); i++) 
-        {
-          PyObject *name, *value;
-          name = PyList_GET_ITEM(slotnames, i);
-          value = PyObject_GetAttr(self, name);
-          if (value == NULL)
-            PyErr_Clear();
-          else 
-            {
-              int err = PyDict_SetItem(slots, name, value);
-              Py_DECREF(value);
-              if (err)
-                goto end;
-              n++;
-            }
-        }
-    }
-
-  if (n) 
-    state = Py_BuildValue("(NO)", state, slots);
-
- end:
-  Py_XDECREF(slotnames);
-  Py_XDECREF(slots);
-  
-  return state;
-}
-
-static int
-Base_setattrs_from_dict(PyObject *self, PyObject *dict)
-{
-  PyObject *key, *value;
-  int pos = 0;
-  
-  if (! PyDict_Check(dict))
-    {
-      PyErr_SetString(PyExc_TypeError, "Expected dictionary");
-      return -1;
-    }
-  
-  while (PyDict_Next(dict, &pos, &key, &value)) 
-    {
-      if (key != NULL && value != NULL &&
-          (PyObject_SetAttr(self, key, value) < 0)
-          )
-        return -1;
-    }
-  return 0;
-}
-
-static PyObject *
-Base___setstate__(PyObject *self, PyObject *state)
-{
-  PyObject *instdict, *slots=NULL;
-
-  if (PyTuple_Check(state))
-    {
-      if (! PyArg_ParseTuple(state, "OO", &state, &slots))
-        return NULL;
-    }
-
-  if (state != Py_None)
-    {
-      instdict = getdict(self);
-      if (instdict != NULL)
-        {
-          PyDict_Clear(instdict);
-          if (PyDict_Update(instdict, state) < 0)
-            return NULL;
-        }
-      else if (Base_setattrs_from_dict(self, state) < 0)
-        return NULL;
-    }
-
-  if (slots != NULL && Base_setattrs_from_dict(self, slots) < 0)
-    return NULL;
-
-  Py_INCREF(Py_None);
-  return Py_None;
-}
-
-static PyObject *
-Base___getnewargs__(PyObject *self)
-{
-  return PyTuple_New(0);
-}
-
-static PyObject *
-Base___reduce__(PyObject *self)
-{
-  PyObject *args=NULL, *bargs=0, *state;
-  int l, i;
-  
-  bargs = PyObject_CallMethodObjArgs(self, str__getnewargs__, NULL);
-  if (bargs == NULL)
-    return NULL;
-
-  l = PyTuple_Size(bargs);
-  if (l < 0)
-    goto end;
-
-  args = PyTuple_New(l+1);
-  if (args == NULL)
-    goto end;
-  
-  Py_INCREF(self->ob_type);
-  PyTuple_SET_ITEM(args, 0, OBJECT(self->ob_type));
-  for (i = 0; i < l; i++)
-    {
-      Py_INCREF(PyTuple_GET_ITEM(bargs, i));
-      PyTuple_SET_ITEM(args, i+1, PyTuple_GET_ITEM(bargs, i));
-    }
-  
-  state = PyObject_CallMethodObjArgs(self, str__getstate__, NULL);
-  if (state == NULL)
-    goto end;
-
-  state = Py_BuildValue("(OON)", __newobj__, args, state);
-
- end:
-  Py_XDECREF(bargs);
-  Py_XDECREF(args);
-
-  return state;
-}
+#include "pickle/pickle.c"
 
 static struct PyMethodDef Base_methods[] = {
-  {"__getstate__", (PyCFunction)Base___getstate__, METH_NOARGS, 
-   "Get object's serialization state"},
-  {"__setstate__", (PyCFunction)Base___setstate__, METH_O, 
-   "Set object's serialization state"},
-  {"__getnewargs__", (PyCFunction)Base___getnewargs__, METH_NOARGS, 
-   "Get arguments to be passed to __new__ to create a new object."},
-  {"__reduce__", (PyCFunction)Base___reduce__, METH_NOARGS, 
-   "Reduce an object to constituent parts for serialization."},
+  PICKLE_METHODS
   {NULL,	 (PyCFunction)NULL, 0, NULL}		/* sentinel */
   };
 
@@ -623,12 +427,135 @@
   return PyObject_CallMethodObjArgs(self, str__new__, self, NULL);
 }
 
+static int
+append_new(PyObject *result, PyObject *v)
+{
+  int contains;
+
+  if (v == OBJECT(&BaseType) || v == OBJECT(&PyBaseObject_Type))
+    return 0;                   /* Don't add these until end */
+  contains = PySequence_Contains(result, v);
+  if (contains != 0)
+    return contains;
+  return PyList_Append(result, v);
+}
+
+static int
+copy_mro(PyObject *mro, PyObject *result)
+{
+  PyObject *base;
+  int i, l;
+
+  l = PyTuple_Size(mro);
+  if (l < 0) 
+    return -1;
+
+  for (i=0; i < l; i++)
+    {
+      base = PyTuple_GET_ITEM(mro, i);
+      if (append_new(result, base) < 0)
+        return -1;
+    }
+  return 0;
+}
+
+static int 
+copy_classic(PyObject *base, PyObject *result)
+{
+  PyObject *bases, *basebase;
+  int i, l, err=-1;
+
+  if (append_new(result, base) < 0)
+    return -1;
+
+  bases = PyObject_GetAttr(base, str__bases__);
+  if (bases == NULL)
+    return -1;
+
+  l = PyTuple_Size(bases);
+  if (l < 0) 
+    goto end;
+
+  for (i=0; i < l; i++)
+    {
+      basebase = PyTuple_GET_ITEM(bases, i);
+      if (copy_classic(basebase, result) < 0)
+        goto end;
+    }
+
+  err = 0;
+ 
+ end:
+  Py_DECREF(bases);
+  return err;
+}
+
+static PyObject *
+mro(PyTypeObject *self)
+{
+  /* Compute an MRO for a class */
+  PyObject *result, *base, *basemro, *mro=NULL;
+  int i, l, err;
+
+  result = PyList_New(0);
+  if (result == NULL)
+    return NULL;
+  if (PyList_Append(result, OBJECT(self)) < 0)
+    goto end;
+  l = PyTuple_Size(self->tp_bases);
+  if (l < 0) 
+    goto end;
+  for (i=0; i < l; i++)
+    {
+      base = PyTuple_GET_ITEM(self->tp_bases, i);
+      if (base == NULL)
+        continue;
+      basemro = PyObject_GetAttr(base, str__mro__);
+      if (basemro != NULL)
+        {
+          /* Type */
+          err = copy_mro(basemro, result);
+          Py_DECREF(basemro);
+          if (err < 0)
+            goto end;
+        }
+      else
+        {
+          PyErr_Clear();
+          if (copy_classic(base, result) < 0)
+            goto end;
+        }
+    }
+
+  if (self != &BaseType && PyList_Append(result, OBJECT(&BaseType)) < 0)
+    goto end;
+
+  if (PyList_Append(result, OBJECT(&PyBaseObject_Type)) < 0)
+    goto end;
+
+  l = PyList_GET_SIZE(result);
+  mro = PyTuple_New(l);
+  if (mro == NULL)
+    goto end;
+
+  for (i=0; i < l; i++)
+    {
+      Py_INCREF(PyList_GET_ITEM(result, i));
+      PyTuple_SET_ITEM(mro, i, PyList_GET_ITEM(result, i));
+    }
+ 
+ end:
+  Py_DECREF(result);
+  return mro;
+}
 
 static struct PyMethodDef EC_methods[] = {
   {"__basicnew__", (PyCFunction)__basicnew__, METH_NOARGS, 
    "Create a new empty object"},
   {"inheritedAttribute", (PyCFunction)inheritedAttribute, METH_O, 
    "Look up an inherited attribute"},
+  {"mro", (PyCFunction)mro, METH_NOARGS, 
+   "Compute an mro using the 'encalsulated base' scheme"},
   {NULL,	 (PyCFunction)NULL, 0, NULL}		/* sentinel */
   };
 
@@ -896,6 +823,9 @@
 {
   PyObject *m, *s;
 
+  if (pickle_setup() < 0)
+    return;
+
 #define DEFINE_STRING(S) \
   if(! (str ## S = PyString_FromString(# S))) return
 
@@ -903,23 +833,10 @@
   DEFINE_STRING(__get__);
   DEFINE_STRING(__class_init__);
   DEFINE_STRING(__init__);
-  DEFINE_STRING(__slotnames__);
-  DEFINE_STRING(__getnewargs__);
-  DEFINE_STRING(__getstate__);
+  DEFINE_STRING(__bases__);
+  DEFINE_STRING(__mro__);
   DEFINE_STRING(__new__);
-
-  m = PyImport_ImportModule("copy_reg");
-  if (m == NULL)
-    return;
-
-  copy_reg_slotnames = PyObject_GetAttrString(m, "_slotnames");
-  if (copy_reg_slotnames == NULL)
-    return;
-
-  __newobj__ = PyObject_GetAttrString(m, "__newobj__");
-  Py_DECREF(m);
-  if (__newobj__ == NULL)
-    return;
+#undef DEFINE_STRING
 
   PyExtensionClassCAPI = &TrueExtensionClassCAPI;
 


=== Zope/lib/python/ExtensionClass/tests.py 1.1.2.3 => 1.1.2.4 ===
--- Zope/lib/python/ExtensionClass/tests.py:1.1.2.3	Wed Oct 29 06:09:17 2003
+++ Zope/lib/python/ExtensionClass/tests.py	Mon Nov  3 10:59:58 2003
@@ -170,13 +170,24 @@
     {}
     """
 
+def cmpattrs(self, other, *attrs):
+    for attr in attrs:
+        if attr[:3] in ('_v_', '_p_'):
+            continue
+        c = cmp(getattr(self, attr, None), getattr(other, attr, None))
+        if c:
+            return c
+    return 0
+
 class Simple(Base):
     def __init__(self, name, **kw):
         self.__name__ = name
         self.__dict__.update(kw)
+        self._v_favorite_color = 'blue'
+        self._p_foo = 'bar'
+
     def __cmp__(self, other):
-        return cmp((self.__class__,  self.__dict__ ),
-                   (other.__class__, other.__dict__))
+        return cmpattrs(self, other, '__class__', *(self.__dict__.keys()))
 
 def test_basic_pickling():
     """
@@ -261,22 +272,21 @@
     """
 
 class Slotted(Base):
-    __slots__ = 's1', 's2'
+    __slots__ = 's1', 's2', '_p_splat', '_v_eek'
     def __init__(self, s1, s2):
         self.s1, self.s2 = s1, s2
+        self._v_eek = 1
+        self._p_splat = 2
 
 class SubSlotted(Slotted):
     __slots__ = 's3', 's4'
     def __init__(self, s1, s2, s3):
-        self.s1, self.s2, self.s3 = s1, s2, s3
+        Slotted.__init__(self, s1, s2)
+        self.s3 = s3
+
         
     def __cmp__(self, other):
-        return cmp(
-            (self.__class__,
-             self.s1, self.s2, self.s3, getattr(self, 's4', 0)),
-            (other.__class__,
-             other.s1, other.s2, other.s3, getattr(other, 's4', 0)),
-            )
+        return cmpattrs(self, other, '__class__', 's1', 's2', 's3', 's4')
 
 
 def test_pickling_w_slots_only():
@@ -323,16 +333,13 @@
     def __init__(self, s1, s2, s3, **kw):
         SubSlotted.__init__(self, s1, s2, s3)
         self.__dict__.update(kw)
+        self._v_favorite_color = 'blue'
+        self._p_foo = 'bar'
         
     def __cmp__(self, other):
-        return cmp(
-            (self.__class__,
-             self.s1, self.s2, self.s3, getattr(self, 's4', 0),
-             self.__dict__ ),
-            (other.__class__,
-             other.s1, other.s2, other.s3, getattr(other, 's4', 0),
-             other.__dict__ ),
-            )
+        return cmpattrs(self, other,
+                        '__class__', 's1', 's2', 's3', 's4',
+                        *(self.__dict__.keys()))
 
 def test_pickling_w_slots():
     """
@@ -374,6 +381,47 @@
     1
 
     """
+
+def test_pickling_w_slots_w_empty_dict():
+    """
+    >>> x = SubSubSlotted('x', 'y', 'z')
+
+    >>> x.__getnewargs__()
+    ()
+
+    >>> d, s = x.__getstate__()
+    >>> print_dict(d)
+    {}
+    >>> print_dict(s)
+    {'s1': 'x', 's2': 'y', 's3': 'z'}
+    
+    >>> pickle.loads(pickle.dumps(x)) == x
+    1
+    >>> pickle.loads(pickle.dumps(x, 0)) == x
+    1
+    >>> pickle.loads(pickle.dumps(x, 1)) == x
+    1
+    >>> pickle.loads(pickle.dumps(x, 2)) == x
+    1
+
+    >>> x.s4 = 'spam'
+    
+    >>> d, s = x.__getstate__()
+    >>> print_dict(d)
+    {}
+    >>> print_dict(s)
+    {'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'}
+
+    >>> pickle.loads(pickle.dumps(x)) == x
+    1
+    >>> pickle.loads(pickle.dumps(x, 0)) == x
+    1
+    >>> pickle.loads(pickle.dumps(x, 1)) == x
+    1
+    >>> pickle.loads(pickle.dumps(x, 2)) == x
+    1
+
+    """
     
 def test_setattr_on_extension_type():
     """
@@ -418,7 +466,156 @@
         """and ends with __ and contains only 4 _ characters
 
     """
-        
+
+def test_mro():
+    """ExtensionClass method-resolution order
+
+    The EC MRO is chosen to maximize backward compatibility and
+    provide a model that is easy to reason about.  The basic idea is:
+
+    I'll call this the "encapsulated base"  scheme.
+
+    Consider:
+
+      >>> class X(Base):
+      ...    pass
+      >>> class Y(Base):
+      ...    pass
+      >>> class Z(Base):
+      ...    pass
+
+      >>> class C(X, Y, Z):
+      ...    def foo(self):
+      ...       return 42
+
+    When we look up an attribute, we do the following:
+
+    - Look in C's dictionary first.
+
+    - Look up the attribute in X.  We don't care how we get the
+      attribute from X. If X is a new-style-class, we use the new
+      algorithm. If X is a classic class, we use left-to-right
+      depth-first. If X is an nsEC, use the "encapsulated base"
+      algorithm.
+
+      If we don't find the attribute in X, look in Y and then in Z,
+      using the same approach.
+
+      This algorithm will produce backward compatible results, providing
+      the equivalent of left-to-right depth-first for nsECs and classic
+      classes.
+
+    We'll actually do something less abstract.  We'll use a simple
+    algorthm to merge the __mro__ of the base classes, computing an
+    __mro__ for classic classes using the left-to-right depth-first
+    algorithm. We'll basically lay the mros end-to-end left-to-right
+    and remove repeats, keeping the first occurence of each class.
+
+    >>> [c.__name__ for c in C.__mro__]
+    ['C', 'X', 'Y', 'Z', 'Base', 'object']
+
+    For backward-compatability's sake, we actually depart from the
+    above description a bit. We always put Base and object last in the
+    mro, as shown in the example above. The primary reason for this is
+    that object provides a do-nothing __init__ method.  It is common
+    practice to mix a C-implemented base class that implements a few
+    methods with a Python class that implements those methods and
+    others. The idea is that the C implementation overrides selected
+    methods in C, so the C subclass is listed first. Unfortunately,
+    because all extension classes are required to subclass Base, and
+    thus, object, the C subclass brings along the __init__ object
+    from objects, which would hide any __init__ method provided by the
+    Python mix-in.
+
+    Base and object are special in that they are implied by their meta
+    classes.   For example, a new-style class always has object as an
+    ancestor, even if it isn't listed as a base:
+
+    >>> class O: 
+    ...     __metaclass__ = type
+    
+    >>> [c.__name__ for c in O.__bases__]
+    ['object']
+    >>> [c.__name__ for c in O.__mro__]
+    ['O', 'object']
+
+    Similarly, Base is always an ancestor of an extension class:
+
+    >>> class E: 
+    ...     __metaclass__ = ExtensionClass
+    
+    >>> [c.__name__ for c in E.__bases__]
+    ['Base']
+    >>> [c.__name__ for c in E.__mro__]
+    ['E', 'Base', 'object']
+    
+    Base and object are generally added soley to get a particular meta
+    class. They aren't used to provide application functionality and
+    really shouldn't be considered when reasoning about where
+    attributes come from.  They do provide some useful default
+    functionality and should be included at the end of the mro.
+
+    Here are more examples:
+
+    >>> from ExtensionClass import Base
+
+    >>> class NA(object):
+    ...  pass
+    >>> class NB(NA):
+    ...  pass
+    >>> class NC(NA):
+    ...  pass
+    >>> class ND(NB, NC):
+    ...  pass
+    >>> [c.__name__ for c in ND.__mro__]
+    ['ND', 'NB', 'NC', 'NA', 'object']
+
+    >>> class EA(Base):
+    ...  pass
+    >>> class EB(EA):
+    ...  pass
+    >>> class EC(EA):
+    ...  pass
+    >>> class ED(EB, EC):
+    ...  pass
+    >>> [c.__name__ for c in ED.__mro__]
+    ['ED', 'EB', 'EA', 'EC', 'Base', 'object']
+
+    >>> class EE(ED, ND):
+    ...  pass
+    >>> [c.__name__ for c in EE.__mro__]
+    ['EE', 'ED', 'EB', 'EA', 'EC', 'ND', 'NB', 'NC', 'NA', 'Base', 'object']
+
+    >>> class EF(ND, ED):
+    ...  pass
+    >>> [c.__name__ for c in EF.__mro__]
+    ['EF', 'ND', 'NB', 'NC', 'NA', 'ED', 'EB', 'EA', 'EC', 'Base', 'object']
+
+    >>> class CA:
+    ...  pass
+    >>> class CB(CA):
+    ...  pass
+    >>> class CC(CA):
+    ...  pass
+    >>> class CD(CB, CC):
+    ...  pass
+
+    >>> class ECD(Base, CD):
+    ...  pass
+    >>> [c.__name__ for c in ECD.__mro__]
+    ['ECD', 'CD', 'CB', 'CA', 'CC', 'Base', 'object']
+
+    >>> class CDE(CD, Base):
+    ...  pass
+    >>> [c.__name__ for c in CDE.__mro__]
+    ['CDE', 'CD', 'CB', 'CA', 'CC', 'Base', 'object']
+
+    >>> class CEND(CD, ED, ND):
+    ...  pass
+    >>> [c.__name__ for c in CEND.__mro__]
+    ['CEND', 'CD', 'CB', 'CA', 'CC', """ \
+       """'ED', 'EB', 'EA', 'EC', 'ND', 'NB', 'NC', 'NA', 'Base', 'object']
+    """
 
 from doctest import DocTestSuite
 import unittest




More information about the Zope-Checkins mailing list