[Zope-Checkins] CVS: Zope/lib/python/ExtensionClass -
ExtensionClass.h:1.2 _ExtensionClass.c:1.2 __init__.py:1.2
setup.py:1.2 tests.py:1.2
Jim Fulton
cvs-admin at zope.org
Fri Nov 28 11:45:33 EST 2003
Update of /cvs-repository/Zope/lib/python/ExtensionClass
In directory cvs.zope.org:/tmp/cvs-serv4161/lib/python/ExtensionClass
Added Files:
ExtensionClass.h _ExtensionClass.c __init__.py setup.py
tests.py
Log Message:
Reimplemented extension classes based on new-style classes.
=== Zope/lib/python/ExtensionClass/ExtensionClass.h 1.1 => 1.2 ===
--- /dev/null Fri Nov 28 11:45:33 2003
+++ Zope/lib/python/ExtensionClass/ExtensionClass.h Fri Nov 28 11:45:02 2003
@@ -0,0 +1,267 @@
+/*****************************************************************************
+
+ Copyright (c) 1996-2002 Zope Corporation and Contributors.
+ All Rights Reserved.
+
+ This software is subject to the provisions of the Zope Public License,
+ Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+ WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+ FOR A PARTICULAR PURPOSE
+
+ ****************************************************************************/
+
+/*
+
+ $Id$
+
+ Extension Class Definitions
+
+ Implementing base extension classes
+
+ A base extension class is implemented in much the same way that an
+ extension type is implemented, except:
+
+ - The include file, 'ExtensionClass.h', must be included.
+
+ - The type structure is declared to be of type
+ 'PyExtensionClass', rather than of type 'PyTypeObject'.
+
+ - The type structure has an additional member that must be defined
+ after the documentation string. This extra member is a method chain
+ ('PyMethodChain') containing a linked list of method definition
+ ('PyMethodDef') lists. Method chains can be used to implement
+ method inheritance in C. Most extensions don't use method chains,
+ but simply define method lists, which are null-terminated arrays
+ of method definitions. A macro, 'METHOD_CHAIN' is defined in
+ 'ExtensionClass.h' that converts a method list to a method chain.
+ (See the example below.)
+
+ - Module functions that create new instances must be replaced by an
+ '__init__' method that initializes, but does not create storage for
+ instances.
+
+ - The extension class must be initialized and exported to the module
+ with::
+
+ PyExtensionClass_Export(d,"name",type);
+
+ where 'name' is the module name and 'type' is the extension class
+ type object.
+
+ Attribute lookup
+
+ Attribute lookup is performed by calling the base extension class
+ 'getattr' operation for the base extension class that includes C
+ data, or for the first base extension class, if none of the base
+ extension classes include C data. 'ExtensionClass.h' defines a
+ macro 'Py_FindAttrString' that can be used to find an object's
+ attributes that are stored in the object's instance dictionary or
+ in the object's class or base classes::
+
+ v = Py_FindAttrString(self,name);
+
+ In addition, a macro is provided that replaces 'Py_FindMethod'
+ calls with logic to perform the same sort of lookup that is
+ provided by 'Py_FindAttrString'.
+
+ Linking
+
+ The extension class mechanism was designed to be useful with
+ dynamically linked extension modules. Modules that implement
+ extension classes do not have to be linked against an extension
+ class library. The macro 'PyExtensionClass_Export' imports the
+ 'ExtensionClass' module and uses objects imported from this module
+ to initialize an extension class with necessary behavior.
+
+*/
+
+#ifndef EXTENSIONCLASS_H
+#define EXTENSIONCLASS_H
+
+#include "Python.h"
+#include "import.h"
+
+/* Declarations for objects of type ExtensionClass */
+
+#define EC PyTypeObject
+#define PyExtensionClass PyTypeObject
+
+#define EXTENSIONCLASS_BINDABLE_FLAG 1 << 2
+#define EXTENSIONCLASS_NOINSTDICT_FLAG 1 << 5
+
+typedef struct {
+ PyObject_HEAD
+} _emptyobject;
+
+static struct ExtensionClassCAPIstruct {
+
+/*****************************************************************************
+
+ WARNING: THIS STRUCT IS PRIVATE TO THE EXTENSION CLASS INTERFACE
+ IMPLEMENTATION AND IS SUBJECT TO CHANGE !!!
+
+ *****************************************************************************/
+
+
+ PyObject *(*EC_findiattrs_)(PyObject *self, char *cname);
+ int (*PyExtensionClass_Export_)(PyObject *dict, char *name,
+ PyTypeObject *typ);
+ PyObject *(*PyECMethod_New_)(PyObject *callable, PyObject *inst);
+ PyExtensionClass *ECBaseType_;
+ PyExtensionClass *ECExtensionClassType_;
+} *PyExtensionClassCAPI = NULL;
+
+#define ECBaseType (PyExtensionClassCAPI->ECBaseType_)
+#define ECExtensionClassType (PyExtensionClassCAPI->ECExtensionClassType_)
+
+/* Following are macros that are needed or useful for defining extension
+ classes:
+ */
+
+/* This macro redefines Py_FindMethod to do attribute for an attribute
+ name given by a C string lookup using extension class meta-data.
+ This is used by older getattr implementations.
+
+ This macro is used in base class implementations of tp_getattr to
+ lookup methods or attributes that are not managed by the base type
+ directly. The macro is generally used to search for attributes
+ after other attribute searches have failed.
+
+ Note that in Python 1.4, a getattr operation may be provided that
+ uses an object argument. Classes that support this new operation
+ should use Py_FindAttr.
+ */
+
+#define EC_findiattrs (PyExtensionClassCAPI->EC_findiattrs_)
+
+#define Py_FindMethod(M,SELF,NAME) (EC_findiattrs((SELF),(NAME)))
+
+/* Do method or attribute lookup for an attribute name given by a C
+ string using extension class meta-data.
+
+ This macro is used in base class implementations of tp_getattro to
+ lookup methods or attributes that are not managed by the base type
+ directly. The macro is generally used to search for attributes
+ after other attribute searches have failed.
+
+ Note that in Python 1.4, a getattr operation may be provided that
+ uses an object argument. Classes that support this new operation
+ should use Py_FindAttr.
+ */
+#define Py_FindAttrString(SELF,NAME) (EC_findiattrs((SELF),(NAME)))
+
+/* Do method or attribute lookup using extension class meta-data.
+
+ This macro is used in base class implementations of tp_getattr to
+ lookup methods or attributes that are not managed by the base type
+ directly. The macro is generally used to search for attributes
+ after other attribute searches have failed. */
+#define Py_FindAttr (ECBaseType->tp_getattro)
+
+/* Do attribute assignment for an attribute.
+
+ This macro is used in base class implementations of tp_setattro to
+ set attributes that are not managed by the base type directly. The
+ macro is generally used to assign attributes after other attribute
+ attempts to assign attributes have failed.
+ */
+#define PyEC_SetAttr(SELF,NAME,V) (ECBaseType->tp_setattro(SELF, NAME, V))
+
+
+/* Convert a method list to a method chain. */
+#define METHOD_CHAIN(DEF) (traverseproc)(DEF)
+
+/* The following macro checks whether a type is an extension class: */
+#define PyExtensionClass_Check(TYPE) \
+ (((PyObject*)(TYPE))->ob_type == ECExtensionClassType)
+
+/* The following macro checks whether an instance is an extension instance: */
+#define PyExtensionInstance_Check(INST) \
+ (((PyObject*)(INST))->ob_type->ob_type == ECExtensionClassType)
+
+#define CHECK_FOR_ERRORS(MESS)
+
+/* The following macro can be used to define an extension base class
+ that only provides method and that is used as a pure mix-in class. */
+#define PURE_MIXIN_CLASS(NAME,DOC,METHODS) \
+static PyExtensionClass NAME ## Type = { PyObject_HEAD_INIT(NULL) 0, # NAME, \
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
+ 0 , DOC, (traverseproc)METHODS, }
+
+/* The following macros provide limited access to extension-class
+ method facilities. */
+
+/* Test for an ExtensionClass method: */
+#define PyECMethod_Check(O) PyMethod_Check((O))
+
+/* Create a method object that wraps a callable object and an
+ instance. Note that if the callable object is an extension class
+ method, then the new method will wrap the callable object that is
+ wrapped by the extension class method. Also note that if the
+ callable object is an extension class method with a reference
+ count of 1, then the callable object will be rebound to the
+ instance and returned with an incremented reference count.
+ */
+#define PyECMethod_New(CALLABLE, INST) \
+ PyExtensionClassCAPI->PyECMethod_New_((CALLABLE),(INST))
+
+/* Return the instance that is bound by an extension class method. */
+#define PyECMethod_Self(M) \
+(PyMethod_Check((M)) ? ((PyMethodObject*)(M))->im_self : NULL)
+
+/* Check whether an object has an __of__ method for returning itself
+ in the context of it's container. */
+#define has__of__(O) ((O)->ob_type->ob_type == ECExtensionClassType \
+ && (O)->ob_type->tp_descr_get != NULL)
+
+/* The following macros are used to check whether an instance
+ or a class' instanses have instance dictionaries: */
+#define HasInstDict(O) (_PyObject_GetDictPtr(O) != NULL)
+
+#define ClassHasInstDict(C) ((C)->tp_dictoffset > 0))
+
+/* Get an object's instance dictionary. Use with caution */
+#define INSTANCE_DICT(inst) (_PyObject_GetDictPtr(O))
+
+/* Test whether an ExtensionClass, S, is a subclass of ExtensionClass C. */
+#define ExtensionClassSubclass_Check(S,C) PyType_IsSubtype((S), (C))
+
+/* Test whether an ExtensionClass instance , I, is a subclass of
+ ExtensionClass C. */
+#define ExtensionClassSubclassInstance_Check(I,C) PyObject_TypeCheck((I), (C))
+
+
+/* Export an Extension Base class in a given module dictionary with a
+ given name and ExtensionClass structure.
+ */
+
+#define PyExtensionClass_Export(D,N,T) \
+ if (! ExtensionClassImported || \
+ PyExtensionClassCAPI->PyExtensionClass_Export_((D),(N),&(T)) < 0) return;
+
+
+#define ExtensionClassImported \
+ ((PyExtensionClassCAPI != NULL) || \
+ (PyExtensionClassCAPI = PyCObject_Import("ExtensionClass","CAPI2")))
+
+
+/* These are being overridded to use tp_free when used with
+ new-style classes. This is to allow old extention-class code
+ to work.
+*/
+
+#undef PyMem_DEL
+#undef PyObject_DEL
+
+#define PyMem_DEL(O) \
+ if (((O)->ob_type->tp_flags & Py_TPFLAGS_HAVE_CLASS) \
+ && ((O)->ob_type->tp_free != NULL)) \
+ (O)->ob_type->tp_free((PyObject*)(O)); \
+ else \
+ PyObject_FREE((O));
+
+#define PyObject_DEL(O) PyMem_DEL(O)
+
+#endif /* EXTENSIONCLASS_H */
=== Zope/lib/python/ExtensionClass/_ExtensionClass.c 1.1 => 1.2 ===
--- /dev/null Fri Nov 28 11:45:33 2003
+++ Zope/lib/python/ExtensionClass/_ExtensionClass.c Fri Nov 28 11:45:02 2003
@@ -0,0 +1,897 @@
+/*
+
+ Copyright (c) 2003 Zope Corporation and Contributors.
+ All Rights Reserved.
+
+ This software is subject to the provisions of the Zope Public License,
+ Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+ THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+ WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+ FOR A PARTICULAR PURPOSE.
+
+*/
+static char _extensionclass_module_documentation[] =
+"ExtensionClass\n"
+"\n"
+"$Id$\n"
+;
+
+#include "ExtensionClass.h"
+
+#define EC PyTypeObject
+
+static PyObject *str__of__, *str__get__, *str__class_init__, *str__init__;
+static PyObject *str__bases__, *str__mro__, *str__new__;
+
+#define OBJECT(O) ((PyObject *)(O))
+#define TYPE(O) ((PyTypeObject *)(O))
+
+extern PyTypeObject ExtensionClassType;
+extern PyTypeObject BaseType;
+
+static PyObject *
+of_get(PyObject *self, PyObject *inst, PyObject *cls)
+{
+ /* Descriptor slot function that calls __of__ */
+ if (inst && PyExtensionInstance_Check(inst))
+ return PyObject_CallMethodObjArgs(self, str__of__, inst, NULL);
+
+ Py_INCREF(self);
+ return self;
+}
+
+PyObject *
+Base_getattro(PyObject *obj, PyObject *name)
+{
+ /* This is a modified copy of PyObject_GenericGetAttr.
+ See the change note below. */
+
+ PyTypeObject *tp = obj->ob_type;
+ PyObject *descr = NULL;
+ PyObject *res = NULL;
+ descrgetfunc f;
+ long dictoffset;
+ PyObject **dictptr;
+
+ if (!PyString_Check(name)){
+#ifdef Py_USING_UNICODE
+ /* The Unicode to string conversion is done here because the
+ existing tp_setattro slots expect a string object as name
+ and we wouldn't want to break those. */
+ if (PyUnicode_Check(name)) {
+ name = PyUnicode_AsEncodedString(name, NULL, NULL);
+ if (name == NULL)
+ return NULL;
+ }
+ else
+#endif
+ {
+ PyErr_SetString(PyExc_TypeError,
+ "attribute name must be string");
+ return NULL;
+ }
+ }
+ else
+ Py_INCREF(name);
+
+ if (tp->tp_dict == NULL) {
+ if (PyType_Ready(tp) < 0)
+ goto done;
+ }
+
+ /* Inline _PyType_Lookup */
+ {
+ int i, n;
+ PyObject *mro, *base, *dict;
+
+ /* Look in tp_dict of types in MRO */
+ mro = tp->tp_mro;
+ assert(mro != NULL);
+ assert(PyTuple_Check(mro));
+ n = PyTuple_GET_SIZE(mro);
+ for (i = 0; i < n; i++) {
+ base = PyTuple_GET_ITEM(mro, i);
+ if (PyClass_Check(base))
+ dict = ((PyClassObject *)base)->cl_dict;
+ else {
+ assert(PyType_Check(base));
+ dict = ((PyTypeObject *)base)->tp_dict;
+ }
+ assert(dict && PyDict_Check(dict));
+ descr = PyDict_GetItem(dict, name);
+ if (descr != NULL)
+ break;
+ }
+ }
+
+ f = NULL;
+ if (descr != NULL &&
+ PyType_HasFeature(descr->ob_type, Py_TPFLAGS_HAVE_CLASS)) {
+ f = descr->ob_type->tp_descr_get;
+ if (f != NULL && PyDescr_IsData(descr)) {
+ res = f(descr, obj, (PyObject *)obj->ob_type);
+ goto done;
+ }
+ }
+
+ /* Inline _PyObject_GetDictPtr */
+ dictoffset = tp->tp_dictoffset;
+ if (dictoffset != 0) {
+ PyObject *dict;
+ if (dictoffset < 0) {
+ int tsize;
+ size_t size;
+
+ tsize = ((PyVarObject *)obj)->ob_size;
+ if (tsize < 0)
+ tsize = -tsize;
+ size = _PyObject_VAR_SIZE(tp, tsize);
+
+ dictoffset += (long)size;
+ assert(dictoffset > 0);
+ assert(dictoffset % SIZEOF_VOID_P == 0);
+ }
+ dictptr = (PyObject **) ((char *)obj + dictoffset);
+ dict = *dictptr;
+ if (dict != NULL) {
+ res = PyDict_GetItem(dict, name);
+ if (res != NULL) {
+
+ /* CHANGED!
+ If the tp_descr_get of res is of_get,
+ then call it. */
+
+ if (res->ob_type->ob_type == &ExtensionClassType
+ && res->ob_type->tp_descr_get != NULL)
+ res = res->ob_type->tp_descr_get(
+ res, obj,
+ OBJECT(obj->ob_type));
+ else
+ Py_INCREF(res);
+ goto done;
+ }
+ }
+ }
+
+ if (f != NULL) {
+ res = f(descr, obj, (PyObject *)obj->ob_type);
+ goto done;
+ }
+
+ if (descr != NULL) {
+ Py_INCREF(descr);
+ res = descr;
+ goto done;
+ }
+
+ /* CHANGED: Just use the name. Don't format. */
+ PyErr_SetObject(PyExc_AttributeError, name);
+ done:
+ Py_DECREF(name);
+ return res;
+}
+
+#include "pickle/pickle.c"
+
+static struct PyMethodDef Base_methods[] = {
+ PICKLE_METHODS
+ {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */
+ };
+
+
+static EC BaseType = {
+ PyObject_HEAD_INIT(NULL)
+ /* ob_size */ 0,
+ /* tp_name */ "ExtensionClass."
+ "Base",
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* tp_getattro */ (getattrofunc)Base_getattro,
+ 0, 0,
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ "Standard ExtensionClass base type",
+ 0, 0, 0, 0, 0, 0,
+ Base_methods,
+};
+
+static EC NoInstanceDictionaryBaseType = {
+ PyObject_HEAD_INIT(NULL)
+ /* ob_size */ 0,
+ /* tp_name */ "ExtensionClass."
+ "NoInstanceDictionaryBase",
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ "Base types for subclasses without instance dictionaries",
+};
+
+static PyObject *
+EC_new(PyTypeObject *self, PyObject *args, PyObject *kw)
+{
+ PyObject *name, *bases=NULL, *dict=NULL;
+ PyObject *new_bases=NULL, *new_args, *result;
+ int have_base = 0, i;
+
+ if (kw && PyObject_IsTrue(kw))
+ {
+ PyErr_SetString(PyExc_TypeError,
+ "Keyword arguments are not supported");
+ return NULL;
+ }
+
+ if (!PyArg_ParseTuple(args, "O|O!O!", &name,
+ &PyTuple_Type, &bases, &PyDict_Type, &dict))
+ return NULL;
+
+ /* Make sure Base is in bases */
+ if (bases)
+ {
+ for (i = 0; i < PyTuple_GET_SIZE(bases); i++)
+ {
+ if (PyObject_TypeCheck(PyTuple_GET_ITEM(bases, i),
+ &ExtensionClassType))
+ {
+ have_base = 1;
+ break;
+ }
+ }
+ if (! have_base)
+ {
+ new_bases = PyTuple_New(PyTuple_GET_SIZE(bases) + 1);
+ if (new_bases == NULL)
+ return NULL;
+ for (i = 0; i < PyTuple_GET_SIZE(bases); i++)
+ {
+ Py_XINCREF(PyTuple_GET_ITEM(bases, i));
+ PyTuple_SET_ITEM(new_bases, i, PyTuple_GET_ITEM(bases, i));
+ }
+ Py_INCREF(OBJECT(&BaseType));
+ PyTuple_SET_ITEM(new_bases, PyTuple_GET_SIZE(bases),
+ OBJECT(&BaseType));
+ }
+ }
+ else
+ {
+ new_bases = Py_BuildValue("(O)", &BaseType);
+ if (new_bases == NULL)
+ return NULL;
+ }
+
+
+
+ if (new_bases)
+ {
+ if (dict)
+ new_args = Py_BuildValue("OOO", name, new_bases, dict);
+ else
+ new_args = Py_BuildValue("OO", name, new_bases);
+
+ Py_DECREF(new_bases);
+
+ if (new_args == NULL)
+ return NULL;
+
+ result = PyType_Type.tp_new(self, new_args, kw);
+ Py_DECREF(new_args);
+ }
+ else
+ {
+ result = PyType_Type.tp_new(self, args, kw);
+
+ /* We didn't have to add Base, so maybe NoInstanceDictionaryBase
+ is in the bases. We need to check if it was. If it was, we
+ need to suppress instance dictionary support. */
+ for (i = 0; i < PyTuple_GET_SIZE(bases); i++)
+ {
+ if (
+ PyObject_TypeCheck(PyTuple_GET_ITEM(bases, i),
+ &ExtensionClassType)
+ &&
+ PyType_IsSubtype(TYPE(PyTuple_GET_ITEM(bases, i)),
+ &NoInstanceDictionaryBaseType)
+ )
+ {
+ TYPE(result)->tp_dictoffset = 0;
+ break;
+ }
+ }
+
+ }
+
+ return result;
+}
+
+static int
+EC_init(PyTypeObject *self, PyObject *args, PyObject *kw)
+{
+ PyObject *__class_init__, *__of__, *r;
+
+ if (PyType_Type.tp_init(OBJECT(self), args, kw) < 0)
+ return -1;
+
+ /* set up __get__, if necessary */
+ if (self->tp_descr_get != of_get)
+ {
+ __of__ = PyObject_GetAttr(OBJECT(self), str__of__);
+ if (__of__)
+ {
+ Py_DECREF(__of__);
+ if (self->tp_descr_get)
+ {
+ PyErr_SetString(PyExc_TypeError,
+ "Can't mix __of__ and descriptors");
+ return -1;
+ }
+ self->tp_descr_get = of_get;
+ }
+ else
+ PyErr_Clear();
+ }
+
+ /* Call __class_init__ */
+ __class_init__ = PyObject_GetAttr(OBJECT(self), str__class_init__);
+ if (__class_init__ == NULL)
+ {
+ PyErr_Clear();
+ return 0;
+ }
+
+ if (! (PyMethod_Check(__class_init__)
+ && PyMethod_GET_FUNCTION(__class_init__)
+ )
+ )
+ {
+ Py_DECREF(__class_init__);
+ PyErr_SetString(PyExc_TypeError, "Invalid type for __class_init__");
+ return -1;
+ }
+
+ r = PyObject_CallFunctionObjArgs(PyMethod_GET_FUNCTION(__class_init__),
+ OBJECT(self), NULL);
+ Py_DECREF(__class_init__);
+ if (! r)
+ return -1;
+ Py_DECREF(r);
+
+ return 0;
+}
+
+static int
+EC_setattro(PyTypeObject *type, PyObject *name, PyObject *value)
+{
+ /* We want to allow setting attributes of builti-in types, because
+ EC did in the past and there's code that relies on it.
+
+ We can't really set slots though, but I don't think we need to.
+ There's no good way to spot slots. We could use a lame rule like
+ names that begin and end with __s and have just 4 _s smell too
+ much like slots.
+
+
+ */
+ if (! (type->tp_flags & Py_TPFLAGS_HEAPTYPE))
+ {
+ char *cname;
+ int l;
+
+ cname = PyString_AsString(name);
+ if (cname == NULL)
+ return -1;
+ l = PyString_GET_SIZE(name);
+ if (l > 4
+ && cname[0] == '_' && cname[1] == '_'
+ && cname[l-1] == '_' && cname[l-2] == '_'
+ )
+ {
+ char *c;
+
+ c = strchr(cname+2, '_');
+ if (c != NULL && (c - cname) >= (l-2))
+ {
+ PyErr_Format
+ (PyExc_TypeError,
+ "can't set attributes of built-in/extension type '%s' if the "
+ "attribute name begins and ends with __ and contains only "
+ "4 _ characters",
+ type->tp_name
+ );
+ return -1;
+ }
+ }
+
+ return PyObject_GenericSetAttr(OBJECT(type), name, value);
+ }
+ return PyType_Type.tp_setattro(OBJECT(type), name, value);
+}
+
+
+static PyObject *
+inheritedAttribute(PyTypeObject *self, PyObject *name)
+{
+ int i;
+ PyObject *d, *cls;
+
+ for (i = 1; i < PyTuple_GET_SIZE(self->tp_mro); i++)
+ {
+ cls = PyTuple_GET_ITEM(self->tp_mro, i);
+ if (PyType_Check(cls))
+ d = ((PyTypeObject *)cls)->tp_dict;
+ else if (PyClass_Check(cls))
+ d = ((PyClassObject *)cls)->cl_dict;
+ else
+ /* Unrecognized thing, punt */
+ d = NULL;
+
+ if ((d == NULL) || (PyDict_GetItem(d, name) == NULL))
+ continue;
+
+ return PyObject_GetAttr(cls, name);
+ }
+
+ PyErr_SetObject(PyExc_AttributeError, name);
+ return NULL;
+}
+
+static PyObject *
+__basicnew__(PyObject *self)
+{
+ 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 */
+ };
+
+
+static PyTypeObject ExtensionClassType = {
+ PyObject_HEAD_INIT(NULL)
+ /* ob_size */ 0,
+ /* tp_name */ "ExtensionClass."
+ "ExtensionClass",
+ /* tp_basicsize */ 0,
+ /* tp_itemsize */ 0,
+ /* tp_dealloc */ (destructor)0,
+ /* tp_print */ (printfunc)0,
+ /* tp_getattr */ (getattrfunc)0,
+ /* tp_setattr */ (setattrfunc)0,
+ /* tp_compare */ (cmpfunc)0,
+ /* tp_repr */ (reprfunc)0,
+ /* tp_as_number */ 0,
+ /* tp_as_sequence */ 0,
+ /* tp_as_mapping */ 0,
+ /* tp_hash */ (hashfunc)0,
+ /* tp_call */ (ternaryfunc)0,
+ /* tp_str */ (reprfunc)0,
+ /* tp_getattro */ (getattrofunc)0,
+ /* tp_setattro */ (setattrofunc)EC_setattro,
+ /* tp_as_buffer */ 0,
+ /* tp_flags */ Py_TPFLAGS_DEFAULT
+ | Py_TPFLAGS_HAVE_GC
+ ,
+ /* tp_doc */ "Meta-class for extension classes",
+ /* tp_traverse */ (traverseproc)0,
+ /* tp_clear */ (inquiry)0,
+ /* tp_richcompare */ (richcmpfunc)0,
+ /* tp_weaklistoffset */ (long)0,
+ /* tp_iter */ (getiterfunc)0,
+ /* tp_iternext */ (iternextfunc)0,
+ /* tp_methods */ EC_methods,
+ /* tp_members */ 0,
+ /* tp_getset */ 0,
+ /* tp_base */ 0,
+ /* tp_dict */ 0, /* internal use */
+ /* tp_descr_get */ (descrgetfunc)0,
+ /* tp_descr_set */ (descrsetfunc)0,
+ /* tp_dictoffset */ 0,
+ /* tp_init */ (initproc)EC_init,
+ /* tp_alloc */ (allocfunc)0,
+ /* tp_new */ (newfunc)EC_new,
+ /* tp_free */ 0, /* Low-level free-mem routine */
+ /* tp_is_gc */ (inquiry)0, /* For PyObject_IS_GC */
+};
+
+static PyObject *
+debug(PyObject *self, PyObject *o)
+{
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+/* List of methods defined in the module */
+
+static struct PyMethodDef ec_methods[] = {
+ {"debug", (PyCFunction)debug, METH_O, ""},
+ {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */
+ };
+
+
+static PyObject *
+EC_findiattrs_(PyObject *self, char *cname)
+{
+ PyObject *name, *r;
+
+ name = PyString_FromString(cname);
+ if (name == NULL)
+ return NULL;
+ r = ECBaseType->tp_getattro(self, name);
+ Py_DECREF(name);
+ return r;
+}
+
+static PyObject *
+ec_new_for_custom_dealloc(PyTypeObject *type, PyObject *args, PyObject *kw)
+{
+ /* This is for EC's that have deallocs. For these, we need to
+ incref the type when we create an instance, because the deallocs
+ will decref the type.
+ */
+
+ PyObject *r;
+
+ r = PyType_GenericNew(type, args, kw);
+ if (r)
+ {
+ Py_INCREF(type);
+ }
+ return r;
+}
+
+static int
+ec_init(PyObject *self, PyObject *args, PyObject *kw)
+{
+ PyObject *r, *__init__;
+
+ __init__ = PyObject_GetAttr(self, str__init__);
+ if (__init__ == NULL)
+ return -1;
+
+ r = PyObject_Call(__init__, args, kw);
+ Py_DECREF(__init__);
+ if (r == NULL)
+ return -1;
+
+ Py_DECREF(r);
+ return 0;
+}
+
+static int
+PyExtensionClass_Export_(PyObject *dict, char *name, PyTypeObject *typ)
+{
+ int ecflags = 0;
+ PyMethodDef *pure_methods = NULL, *mdef = NULL;
+ PyObject *m;
+
+ if (typ->tp_flags == 0)
+ {
+ /* Old-style EC */
+
+ if (typ->tp_traverse)
+ {
+ /* ExtensionClasses stick there methods in the tp_traverse slot */
+ mdef = (PyMethodDef *)typ->tp_traverse;
+
+ if (typ->tp_basicsize <= sizeof(_emptyobject))
+ /* Pure mixin. We want rebindable methods */
+ pure_methods = mdef;
+ else
+ typ->tp_methods = mdef;
+
+ typ->tp_traverse = NULL;
+
+ /* Look for __init__ method */
+ for (; mdef->ml_name; mdef++)
+ {
+ if (strcmp(mdef->ml_name, "__init__") == 0)
+ {
+ /* we have an old-style __init__, install a special slot */
+ typ->tp_init = ec_init;
+ break;
+ }
+ }
+ }
+
+ if (typ->tp_clear)
+ {
+ /* ExtensionClasses stick there flags in the tp_clear slot */
+ ecflags = (int)(typ->tp_clear);
+
+ /* Some old-style flags were set */
+
+ if ((ecflags & EXTENSIONCLASS_BINDABLE_FLAG)
+ && typ->tp_descr_get == NULL)
+ /* We have __of__-style binding */
+ typ->tp_descr_get = of_get;
+ }
+ typ->tp_clear = NULL;
+ typ->tp_flags = Py_TPFLAGS_DEFAULT
+ | Py_TPFLAGS_BASETYPE;
+
+ if (typ->tp_dealloc != NULL)
+ typ->tp_new = ec_new_for_custom_dealloc;
+ }
+
+ typ->ob_type = ECExtensionClassType;
+
+ if (ecflags & EXTENSIONCLASS_NOINSTDICT_FLAG)
+ typ->tp_base = &NoInstanceDictionaryBaseType;
+ else
+ typ->tp_base = &BaseType;
+
+ if (typ->tp_new == NULL)
+ typ->tp_new = PyType_GenericNew;
+
+ if (PyType_Ready(typ) < 0)
+ return -1;
+
+ if (pure_methods)
+ {
+ /* We had pure methods. We want to be able to rebind these, so
+ we'll make them ordinary method wrappers around method descrs
+ */
+ for (; pure_methods->ml_name; pure_methods++)
+ {
+ m = PyDescr_NewMethod(ECBaseType, pure_methods);
+ if (! m)
+ return -1;
+ m = PyMethod_New((PyObject *)m, NULL, (PyObject *)ECBaseType);
+ if (! m)
+ return -1;
+ if (PyDict_SetItemString(typ->tp_dict, pure_methods->ml_name, m)
+ < 0)
+ return -1;
+ }
+ }
+ else if (mdef && mdef->ml_name)
+ {
+ /* Blast, we have to stick __init__ in the dict ourselves
+ because PyType_Ready probably stuck a wrapper for ec_init in
+ instead.
+ */
+ m = PyDescr_NewMethod(typ, mdef);
+ if (! m)
+ return -1;
+ if (PyDict_SetItemString(typ->tp_dict, mdef->ml_name, m) < 0)
+ return -1;
+ }
+
+ if (PyMapping_SetItemString(dict, name, (PyObject*)typ) < 0)
+ return -1;
+
+ return 0;
+}
+
+PyObject *
+PyECMethod_New_(PyObject *callable, PyObject *inst)
+{
+ if (! PyExtensionInstance_Check(inst))
+ {
+ PyErr_SetString(PyExc_TypeError,
+ "Can't bind non-ExtensionClass instance.");
+ return NULL;
+ }
+
+ if (PyMethod_Check(callable))
+ {
+ if (callable->ob_refcnt == 1)
+ {
+ Py_XDECREF(((PyMethodObject*)callable)->im_self);
+ Py_INCREF(inst);
+ ((PyMethodObject*)callable)->im_self = inst;
+ Py_INCREF(callable);
+ return callable;
+ }
+ else
+ return callable->ob_type->tp_descr_get(
+ callable, inst,
+ ((PyMethodObject*)callable)->im_class);
+ }
+ else
+ return PyMethod_New(callable, inst, (PyObject*)(ECBaseType));
+}
+
+static struct ExtensionClassCAPIstruct
+TrueExtensionClassCAPI = {
+ EC_findiattrs_,
+ PyExtensionClass_Export_,
+ PyECMethod_New_,
+ &BaseType,
+ &ExtensionClassType,
+};
+
+#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
+#define PyMODINIT_FUNC void
+#endif
+PyMODINIT_FUNC
+init_ExtensionClass(void)
+{
+ PyObject *m, *s;
+
+ if (pickle_setup() < 0)
+ return;
+
+#define DEFINE_STRING(S) \
+ if(! (str ## S = PyString_FromString(# S))) return
+
+ DEFINE_STRING(__of__);
+ DEFINE_STRING(__get__);
+ DEFINE_STRING(__class_init__);
+ DEFINE_STRING(__init__);
+ DEFINE_STRING(__bases__);
+ DEFINE_STRING(__mro__);
+ DEFINE_STRING(__new__);
+#undef DEFINE_STRING
+
+ PyExtensionClassCAPI = &TrueExtensionClassCAPI;
+
+ ExtensionClassType.ob_type = &PyType_Type;
+ ExtensionClassType.tp_base = &PyType_Type;
+ ExtensionClassType.tp_traverse = PyType_Type.tp_traverse;
+ ExtensionClassType.tp_clear = PyType_Type.tp_clear;
+
+ /* Initialize types: */
+ if (PyType_Ready(&ExtensionClassType) < 0)
+ return;
+
+ BaseType.ob_type = &ExtensionClassType;
+ BaseType.tp_base = &PyBaseObject_Type;
+ BaseType.tp_new = PyType_GenericNew;
+
+ if (PyType_Ready(&BaseType) < 0)
+ return;
+
+ NoInstanceDictionaryBaseType.ob_type = &ExtensionClassType;
+ NoInstanceDictionaryBaseType.tp_base = &BaseType;
+ NoInstanceDictionaryBaseType.tp_new = PyType_GenericNew;
+
+ if (PyType_Ready(&NoInstanceDictionaryBaseType) < 0)
+ return;
+
+ /* Create the module and add the functions */
+ m = Py_InitModule3("_ExtensionClass", ec_methods,
+ _extensionclass_module_documentation);
+
+ if (m == NULL)
+ return;
+
+ s = PyCObject_FromVoidPtr(PyExtensionClassCAPI, NULL);
+ if (PyModule_AddObject(m, "CAPI2", s) < 0)
+ return;
+
+ /* Add types: */
+ if (PyModule_AddObject(m, "ExtensionClass",
+ (PyObject *)&ExtensionClassType) < 0)
+ return;
+ if (PyModule_AddObject(m, "Base", (PyObject *)&BaseType) < 0)
+ return;
+ if (PyModule_AddObject(m, "NoInstanceDictionaryBase",
+ (PyObject *)&NoInstanceDictionaryBaseType) < 0)
+ return;
+}
+
=== Zope/lib/python/ExtensionClass/__init__.py 1.1 => 1.2 ===
--- /dev/null Fri Nov 28 11:45:33 2003
+++ Zope/lib/python/ExtensionClass/__init__.py Fri Nov 28 11:45:02 2003
@@ -0,0 +1,105 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""ExtensionClass
+
+Extension Class exists to support types derived from the old ExtensionType
+meta-class that preceeded Python 2.2 and new-style classes.
+
+As a meta-class, ExtensionClass provides the following features:
+
+- Support for a class initialiser:
+
+ >>> from ExtensionClass import ExtensionClass, Base
+
+ >>> class C(Base):
+ ... def __class_init__(self):
+ ... print 'class init called'
+ ... print self.__name__
+ ... def bar(self):
+ ... return 'bar called'
+ class init called
+ C
+ >>> c = C()
+ >>> int(c.__class__ is C)
+ 1
+ >>> int(c.__class__ is type(c))
+ 1
+
+- Making sure that every instance of the meta-class has Base as a base class:
+
+ >>> class X:
+ ... __metaclass__ = ExtensionClass
+
+ >>> Base in X.__mro__
+ 1
+
+- Provide an inheritedAttribute method for looking up attributes in
+ base classes:
+
+ >>> class C2(C):
+ ... def bar(*a):
+ ... return C2.inheritedAttribute('bar')(*a), 42
+ class init called
+ C2
+ >>> o = C2()
+ >>> o.bar()
+ ('bar called', 42)
+
+ This is for compatability with old code. New code should use super
+ instead.
+
+The base class, Base, exists mainly to support the __of__ protocol.
+The __of__ protocol is similar to __get__ except that __of__ is called
+when an implementor is retrieved from an instance as well as from a
+class:
+
+>>> class O(Base):
+... def __of__(*a):
+... return a
+
+>>> o1 = O()
+>>> o2 = O()
+>>> C.o1 = o1
+>>> c.o2 = o2
+>>> c.o1 == (o1, c)
+1
+>>> C.o1 == o1
+1
+>>> int(c.o2 == (o2, c))
+1
+
+We accomplish this by making a class that implements __of__ a
+descriptor and treating all descriptor ExtensionClasses this way. That
+is, if an extension class is a descriptor, it's __get__ method will be
+called even when it is retrieved from an instance.
+
+>>> class O(Base):
+... def __get__(*a):
+... return a
+...
+>>> o1 = O()
+>>> o2 = O()
+>>> C.o1 = o1
+>>> c.o2 = o2
+>>> int(c.o1 == (o1, c, type(c)))
+1
+>>> int(C.o1 == (o1, None, type(c)))
+1
+>>> int(c.o2 == (o2, c, type(c)))
+1
+
+$Id$
+"""
+
+from _ExtensionClass import *
=== Zope/lib/python/ExtensionClass/setup.py 1.1 => 1.2 ===
--- /dev/null Fri Nov 28 11:45:33 2003
+++ Zope/lib/python/ExtensionClass/setup.py Fri Nov 28 11:45:02 2003
@@ -0,0 +1,8 @@
+from distutils.core import setup, Extension
+setup(name="ExtensionClass", version="2.0",
+ ext_modules=[
+ Extension("_ExtensionClass", ["_ExtensionClass.c"],
+ depends = ["ExtensionClass.h", "pickle/pickle.c"],
+ ),
+ ])
+
=== Zope/lib/python/ExtensionClass/tests.py 1.1 => 1.2 ===
--- /dev/null Fri Nov 28 11:45:33 2003
+++ Zope/lib/python/ExtensionClass/tests.py Fri Nov 28 11:45:02 2003
@@ -0,0 +1,705 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+from ExtensionClass import *
+import pickle
+
+
+def print_dict(d):
+ d = d.items()
+ d.sort()
+ print '{%s}' % (', '.join(
+ [('%r: %r' % (k, v)) for (k, v) in d]
+ ))
+
+def test_mixing():
+ """Test working with a classic class
+
+ >>> class Classic:
+ ... def x(self):
+ ... return 42
+
+ >>> class O(Base):
+ ... def __of__(*a):
+ ... return a
+
+ >>> class O2(Classic, O):
+ ... def __of__(*a):
+ ... return (O2.inheritedAttribute('__of__')(*a),
+ ... O2.inheritedAttribute('x')(a[0]))
+
+ >>> class C(Base):
+ ... def __class_init__(self):
+ ... print 'class init called'
+ ... print self.__name__
+ ... def bar(self):
+ ... return 'bar called'
+ class init called
+ C
+
+ >>> c = C()
+ >>> o2 = O2()
+ >>> c.o2 = o2
+ >>> int(c.o2 == ((o2, c), 42))
+ 1
+
+ Test working with a new style
+
+ >>> class Modern(object):
+ ... def x(self):
+ ... return 42
+
+ >>> class O2(Modern, O):
+ ... def __of__(*a):
+ ... return (O2.inheritedAttribute('__of__')(*a),
+ ... O2.inheritedAttribute('x')(a[0]))
+
+ >>> o2 = O2()
+ >>> c.o2 = o2
+ >>> int(c.o2 == ((o2, c), 42))
+ 1
+
+ """
+
+def test_class_creation_under_stress():
+ """
+ >>> for i in range(100):
+ ... class B(Base):
+ ... print i,
+ ... if i and i%20 == 0:
+ ... print
+ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
+ 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
+ 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
+ 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
+ 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
+
+ >>> import gc
+ >>> x = gc.collect()
+
+ """
+
+def old_test_add():
+ """test_add.py from old EC
+
+ >>> class foo(Base):
+ ... def __add__(self,other): print 'add called'
+
+
+ >>> foo()+foo()
+ add called
+ """
+
+def proper_error_on_deleattr():
+ """
+ Florent Guillaume wrote:
+
+ ...
+
+ Excellent.
+ Will it also fix this particularity of ExtensionClass:
+
+
+ >>> class A(Base):
+ ... def foo(self):
+ ... self.gee
+ ... def bar(self):
+ ... del self.gee
+
+ >>> a=A()
+ >>> a.foo()
+ Traceback (most recent call last):
+ ...
+ AttributeError: gee
+
+ >>> a.bar()
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'A' object has no attribute 'gee'
+
+ I.e., the fact that KeyError is raised whereas a normal class would
+ raise AttributeError.
+ """
+
+def test_NoInstanceDictionaryBase():
+ """
+ >>> class B(NoInstanceDictionaryBase): pass
+ ...
+ >>> B().__dict__
+ Traceback (most recent call last):
+ ...
+ AttributeError: This object has no __dict__
+ >>> class B(NoInstanceDictionaryBase):
+ ... __slots__ = ('a', 'b')
+ ...
+ >>> class BB(B): pass
+ ...
+ >>> b = BB()
+ >>> b.__dict__
+ Traceback (most recent call last):
+ ...
+ AttributeError: This object has no __dict__
+ >>> b.a = 1
+ >>> b.b = 2
+ >>> b.a
+ 1
+ >>> b.b
+ 2
+
+ """
+
+def test__basicnew__():
+ """
+ >>> x = Simple.__basicnew__()
+ >>> x.__dict__
+ {}
+ """
+
+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 cmpattrs(self, other, '__class__', *(self.__dict__.keys()))
+
+def test_basic_pickling():
+ """
+ >>> x = Simple('x', aaa=1, bbb='foo')
+
+ >>> x.__getnewargs__()
+ ()
+
+ >>> print_dict(x.__getstate__())
+ {'__name__': 'x', 'aaa': 1, 'bbb': 'foo'}
+
+ >>> f, (c,), state = x.__reduce__()
+ >>> f.__name__
+ '__newobj__'
+ >>> f.__module__
+ 'copy_reg'
+ >>> c.__name__
+ 'Simple'
+
+ >>> print_dict(state)
+ {'__name__': 'x', 'aaa': 1, 'bbb': 'foo'}
+
+ >>> 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.__setstate__({'z': 1})
+ >>> x.__dict__
+ {'z': 1}
+
+ """
+
+class Custom(Simple):
+
+ def __new__(cls, x, y):
+ r = Base.__new__(cls)
+ r.x, r.y = x, y
+ return r
+
+ def __init__(self, x, y):
+ self.a = 42
+
+ def __getnewargs__(self):
+ return self.x, self.y
+
+ def __getstate__(self):
+ return self.a
+
+ def __setstate__(self, a):
+ self.a = a
+
+
+def test_pickling_w_overrides():
+ """
+ >>> x = Custom('x', 'y')
+ >>> x.a = 99
+
+ >>> (f, (c, ax, ay), a) = x.__reduce__()
+ >>> f.__name__
+ '__newobj__'
+ >>> f.__module__
+ 'copy_reg'
+ >>> c.__name__
+ 'Custom'
+ >>> ax, ay, a
+ ('x', 'y', 99)
+
+ >>> 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
+
+ """
+
+class Slotted(Base):
+ __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):
+ Slotted.__init__(self, s1, s2)
+ self.s3 = s3
+
+
+ def __cmp__(self, other):
+ return cmpattrs(self, other, '__class__', 's1', 's2', 's3', 's4')
+
+
+def test_pickling_w_slots_only():
+ """
+ >>> x = SubSlotted('x', 'y', 'z')
+
+ >>> x.__getnewargs__()
+ ()
+
+ >>> d, s = x.__getstate__()
+ >>> 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__()
+ >>> 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
+
+ """
+
+class SubSubSlotted(SubSlotted):
+
+ 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 cmpattrs(self, other,
+ '__class__', 's1', 's2', 's3', 's4',
+ *(self.__dict__.keys()))
+
+def test_pickling_w_slots():
+ """
+ >>> x = SubSubSlotted('x', 'y', 'z', aaa=1, bbb='foo')
+
+ >>> x.__getnewargs__()
+ ()
+
+ >>> d, s = x.__getstate__()
+ >>> print_dict(d)
+ {'aaa': 1, 'bbb': 'foo'}
+ >>> 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)
+ {'aaa': 1, 'bbb': 'foo'}
+ >>> 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_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():
+ """
+ >>> for name in 'x', '_x', 'x_', '__x_y__', '___x__', '__x___', '_x_':
+ ... setattr(Base, name, 1)
+ ... print getattr(Base, name)
+ ... delattr(Base, name)
+ ... print getattr(Base, name, 0)
+ 1
+ 0
+ 1
+ 0
+ 1
+ 0
+ 1
+ 0
+ 1
+ 0
+ 1
+ 0
+ 1
+ 0
+
+ >>> Base.__foo__ = 1
+ Traceback (most recent call last):
+ ...
+ TypeError: can't set attributes of built-in/extension type """ \
+ """'ExtensionClass.Base' if the attribute name begins """ \
+ """and ends with __ and contains only 4 _ characters
+
+ >>> Base.__foo__
+ Traceback (most recent call last):
+ ...
+ AttributeError: type object 'ExtensionClass.Base' """ \
+ """has no attribute '__foo__'
+
+ >>> del Base.__foo__
+ Traceback (most recent call last):
+ ...
+ TypeError: can't set attributes of built-in/extension type """ \
+ """'ExtensionClass.Base' if the attribute name begins """ \
+ """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']
+ """
+
+def test_avoiding___init__decoy_w_inheritedAttribute():
+ """
+
+ >>> class Decoy(Base):
+ ... pass
+
+ >>> class B(Base):
+ ... def __init__(self, a, b):
+ ... print '__init__', a, b
+
+ >>> class C(Decoy, B):
+ ... def __init__(self):
+ ... print 'C init'
+ ... C.inheritedAttribute('__init__')(self, 1, 2)
+
+ >>> x = C()
+ C init
+ __init__ 1 2
+
+ """
+
+def test_of_not_called_when_not_accessed_through_EC_instance():
+ """
+
+ >>> class Eek(Base):
+ ... def __of__(self, parent):
+ ... return self, parent
+
+ If I define an EC instance as an attr of an ordinary class:
+
+ >>> class O(object):
+ ... eek = Eek()
+
+ >>> class C:
+ ... eek = Eek()
+
+ I get the instance, without calling __of__, when I get it from
+ either tha class:
+
+ >>> O.eek is O.__dict__['eek']
+ True
+
+ >>> C.eek is C.__dict__['eek']
+ True
+
+ or an instance of the class:
+
+ >>> O().eek is O.__dict__['eek']
+ True
+
+ >>> C().eek is C.__dict__['eek']
+ True
+
+ If I define an EC instance as an attr of an extension class:
+
+ >>> class E(Base):
+ ... eek = Eek()
+
+
+ I get the instance, without calling __of__, when I get it from
+ tha class:
+
+ >>> E.eek is E.__dict__['eek']
+ True
+
+ But __of__ is called if I go through the instance:
+
+ >>> e = E()
+ >>> e.eek == (E.__dict__['eek'], e)
+ True
+
+ """
+
+from doctest import DocTestSuite
+import unittest
+
+def test_suite():
+ return unittest.TestSuite((
+ DocTestSuite('ExtensionClass'),
+ DocTestSuite(),
+ ))
+
+if __name__ == '__main__': unittest.main()
+
+
+
More information about the Zope-Checkins
mailing list