[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