summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Martelli <aleaxit@gmail.com>2005-02-07 12:39:55 (GMT)
committerAlex Martelli <aleaxit@gmail.com>2005-02-07 12:39:55 (GMT)
commit4c337993c5afdfd9ebb40ff0847cf6015a875f4f (patch)
treeb41dab2d8307066e1df34d7d4eab210ac1a2f6d1
parent339870121926f56294d8ccbca381d5d29c07cac0 (diff)
downloadcpython-4c337993c5afdfd9ebb40ff0847cf6015a875f4f.zip
cpython-4c337993c5afdfd9ebb40ff0847cf6015a875f4f.tar.gz
cpython-4c337993c5afdfd9ebb40ff0847cf6015a875f4f.tar.bz2
forwardport of 2.3.5 fixes to copy.py
-rw-r--r--Lib/copy.py22
-rw-r--r--Lib/test/test_copy.py101
-rw-r--r--Modules/_testcapimodule.c176
3 files changed, 295 insertions, 4 deletions
diff --git a/Lib/copy.py b/Lib/copy.py
index af905f3..45fc32d 100644
--- a/Lib/copy.py
+++ b/Lib/copy.py
@@ -62,6 +62,16 @@ except ImportError:
__all__ = ["Error", "copy", "deepcopy"]
+import inspect
+def _getspecial(cls, name):
+ for basecls in inspect.getmro(cls):
+ try:
+ return basecls.__dict__[name]
+ except:
+ pass
+ else:
+ return None
+
def copy(x):
"""Shallow copy operation on arbitrary Python objects.
@@ -74,7 +84,7 @@ def copy(x):
if copier:
return copier(x)
- copier = getattr(cls, "__copy__", None)
+ copier = _getspecial(cls, "__copy__")
if copier:
return copier(x)
@@ -90,6 +100,9 @@ def copy(x):
if reductor:
rv = reductor()
else:
+ copier = getattr(x, "__copy__", None)
+ if copier:
+ return copier()
raise Error("un(shallow)copyable object of type %s" % cls)
return _reconstruct(x, rv, 0)
@@ -167,9 +180,9 @@ def deepcopy(x, memo=None, _nil=[]):
if issc:
y = _deepcopy_atomic(x, memo)
else:
- copier = getattr(x, "__deepcopy__", None)
+ copier = _getspecial(cls, "__deepcopy__")
if copier:
- y = copier(memo)
+ y = copier(x, memo)
else:
reductor = dispatch_table.get(cls)
if reductor:
@@ -183,6 +196,9 @@ def deepcopy(x, memo=None, _nil=[]):
if reductor:
rv = reductor()
else:
+ copier = getattr(x, "__deepcopy__", None)
+ if copier:
+ return copier(memo)
raise Error(
"un(deep)copyable object of type %s" % cls)
y = _reconstruct(x, rv, 1, memo)
diff --git a/Lib/test/test_copy.py b/Lib/test/test_copy.py
index bd5a3e1..02dede4 100644
--- a/Lib/test/test_copy.py
+++ b/Lib/test/test_copy.py
@@ -166,6 +166,107 @@ class TestCopy(unittest.TestCase):
x = C(42)
self.assertEqual(copy.copy(x), x)
+ # tests for copying extension types, iff module trycopy is installed
+ def test_copy_classictype(self):
+ from _testcapi import make_copyable
+ x = make_copyable([23])
+ y = copy.copy(x)
+ self.assertEqual(x, y)
+ self.assertEqual(x.tag, y.tag)
+ self.assert_(x is not y)
+ self.assert_(x.tag is y.tag)
+
+ def test_deepcopy_classictype(self):
+ from _testcapi import make_copyable
+ x = make_copyable([23])
+ y = copy.deepcopy(x)
+ self.assertEqual(x, y)
+ self.assertEqual(x.tag, y.tag)
+ self.assert_(x is not y)
+ self.assert_(x.tag is not y.tag)
+
+ # regression tests for class-vs-instance and metaclass-confusion
+ def test_copy_classoverinstance(self):
+ class C(object):
+ def __init__(self, v):
+ self.v = v
+ def __cmp__(self, other):
+ return -cmp(other, self.v)
+ def __copy__(self):
+ return self.__class__(self.v)
+ x = C(23)
+ self.assertEqual(copy.copy(x), x)
+ x.__copy__ = lambda: 42
+ self.assertEqual(copy.copy(x), x)
+
+ def test_deepcopy_classoverinstance(self):
+ class C(object):
+ def __init__(self, v):
+ self.v = v
+ def __cmp__(self, other):
+ return -cmp(other, self.v)
+ def __deepcopy__(self, memo):
+ return self.__class__(copy.deepcopy(self.v, memo))
+ x = C(23)
+ self.assertEqual(copy.deepcopy(x), x)
+ x.__deepcopy__ = lambda memo: 42
+ self.assertEqual(copy.deepcopy(x), x)
+
+
+ def test_copy_metaclassconfusion(self):
+ class MyOwnError(copy.Error):
+ pass
+ class Meta(type):
+ def __copy__(cls):
+ raise MyOwnError("can't copy classes w/this metaclass")
+ class C:
+ __metaclass__ = Meta
+ def __init__(self, tag):
+ self.tag = tag
+ def __cmp__(self, other):
+ return -cmp(other, self.tag)
+ # the metaclass can forbid shallow copying of its classes
+ self.assertRaises(MyOwnError, copy.copy, C)
+ # check that there is no interference with instances
+ x = C(23)
+ self.assertEqual(copy.copy(x), x)
+
+ def test_deepcopy_metaclassconfusion(self):
+ class MyOwnError(copy.Error):
+ pass
+ class Meta(type):
+ def __deepcopy__(cls, memo):
+ raise MyOwnError("can't deepcopy classes w/this metaclass")
+ class C:
+ __metaclass__ = Meta
+ def __init__(self, tag):
+ self.tag = tag
+ def __cmp__(self, other):
+ return -cmp(other, self.tag)
+ # types are ALWAYS deepcopied atomically, no matter what
+ self.assertEqual(copy.deepcopy(C), C)
+ # check that there is no interference with instances
+ x = C(23)
+ self.assertEqual(copy.deepcopy(x), x)
+
+ def _nomro(self):
+ class C(type):
+ def __getattribute__(self, attr):
+ if attr == '__mro__':
+ raise AttributeError, "What, *me*, a __mro__? Nevah!"
+ return super(C, self).__getattribute__(attr)
+ class D(object):
+ __metaclass__ = C
+ return D()
+
+ def test_copy_mro(self):
+ x = self._nomro()
+ y = copy.copy(x)
+
+ def test_deepcopy_mro(self):
+ x = self._nomro()
+ y = copy.deepcopy(x)
+
# The deepcopy() method
def test_deepcopy_basic(self):
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 9a5d885..fab456a 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -588,6 +588,169 @@ test_thread_state(PyObject *self, PyObject *args)
}
#endif
+/* a classic-type with copyable instances */
+
+typedef struct {
+ PyObject_HEAD
+ /* instance tag (a string). */
+ PyObject* tag;
+} CopyableObject;
+
+staticforward PyTypeObject Copyable_Type;
+
+#define Copyable_CheckExact(op) ((op)->ob_type == &Copyable_Type)
+
+/* -------------------------------------------------------------------- */
+
+/* copyable constructor and destructor */
+static PyObject*
+copyable_new(PyObject* tag)
+{
+ CopyableObject* self;
+
+ self = PyObject_New(CopyableObject, &Copyable_Type);
+ if (self == NULL)
+ return NULL;
+ Py_INCREF(tag);
+ self->tag = tag;
+ return (PyObject*) self;
+}
+
+static PyObject*
+copyable(PyObject* self, PyObject* args, PyObject* kw)
+{
+ PyObject* elem;
+ PyObject* tag;
+ if (!PyArg_ParseTuple(args, "O:Copyable", &tag))
+ return NULL;
+ elem = copyable_new(tag);
+ return elem;
+}
+
+static void
+copyable_dealloc(CopyableObject* self)
+{
+ /* discard attributes */
+ Py_DECREF(self->tag);
+ PyObject_Del(self);
+}
+
+/* copyable methods */
+
+static PyObject*
+copyable_copy(CopyableObject* self, PyObject* args)
+{
+ CopyableObject* copyable;
+ if (!PyArg_ParseTuple(args, ":__copy__"))
+ return NULL;
+ copyable = (CopyableObject*)copyable_new(self->tag);
+ if (!copyable)
+ return NULL;
+ return (PyObject*) copyable;
+}
+
+PyObject* _copy_deepcopy;
+
+static PyObject*
+copyable_deepcopy(CopyableObject* self, PyObject* args)
+{
+ CopyableObject* copyable = 0;
+ PyObject* memo;
+ PyObject* tag_copy;
+ if (!PyArg_ParseTuple(args, "O:__deepcopy__", &memo))
+ return NULL;
+
+ tag_copy = PyObject_CallFunctionObjArgs(_copy_deepcopy, self->tag, memo, NULL);
+
+ if(tag_copy) {
+ copyable = (CopyableObject*)copyable_new(tag_copy);
+ Py_DECREF(tag_copy);
+ }
+ return (PyObject*) copyable;
+}
+
+static PyObject*
+copyable_repr(CopyableObject* self)
+{
+ PyObject* repr;
+ char buffer[100];
+
+ repr = PyString_FromString("<Copyable {");
+
+ PyString_ConcatAndDel(&repr, PyObject_Repr(self->tag));
+
+ sprintf(buffer, "} at %p>", self);
+ PyString_ConcatAndDel(&repr, PyString_FromString(buffer));
+
+ return repr;
+}
+
+static int
+copyable_compare(CopyableObject* obj1, CopyableObject* obj2)
+{
+ return PyObject_Compare(obj1->tag, obj2->tag);
+}
+
+static PyMethodDef copyable_methods[] = {
+ {"__copy__", (PyCFunction) copyable_copy, METH_VARARGS},
+ {"__deepcopy__", (PyCFunction) copyable_deepcopy, METH_VARARGS},
+ {NULL, NULL}
+};
+
+static PyObject*
+copyable_getattr(CopyableObject* self, char* name)
+{
+ PyObject* res;
+ res = Py_FindMethod(copyable_methods, (PyObject*) self, name);
+ if (res)
+ return res;
+ PyErr_Clear();
+ if (strcmp(name, "tag") == 0) {
+ res = self->tag;
+ } else {
+ PyErr_SetString(PyExc_AttributeError, name);
+ return NULL;
+ }
+ if (!res)
+ return NULL;
+ Py_INCREF(res);
+ return res;
+}
+
+static int
+copyable_setattr(CopyableObject* self, const char* name, PyObject* value)
+{
+ if (value == NULL) {
+ PyErr_SetString(
+ PyExc_AttributeError,
+ "can't delete copyable attributes"
+ );
+ return -1;
+ }
+ if (strcmp(name, "tag") == 0) {
+ Py_DECREF(self->tag);
+ self->tag = value;
+ Py_INCREF(self->tag);
+ } else {
+ PyErr_SetString(PyExc_AttributeError, name);
+ return -1;
+ }
+ return 0;
+}
+
+statichere PyTypeObject Copyable_Type = {
+ PyObject_HEAD_INIT(NULL)
+ 0, "Copyable", sizeof(CopyableObject), 0,
+ /* methods */
+ (destructor)copyable_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ (getattrfunc)copyable_getattr, /* tp_getattr */
+ (setattrfunc)copyable_setattr, /* tp_setattr */
+ (cmpfunc)copyable_compare, /* tp_compare */
+ (reprfunc)copyable_repr, /* tp_repr */
+ 0, /* tp_as_number */
+};
+
static PyMethodDef TestMethods[] = {
{"raise_exception", raise_exception, METH_VARARGS},
{"test_config", (PyCFunction)test_config, METH_NOARGS},
@@ -614,9 +777,11 @@ static PyMethodDef TestMethods[] = {
{"test_u_code", (PyCFunction)test_u_code, METH_NOARGS},
#endif
#ifdef WITH_THREAD
- {"_test_thread_state", (PyCFunction)test_thread_state, METH_VARARGS},
+ {"_test_thread_state", (PyCFunction)test_thread_state, METH_VARARGS},
#endif
+ {"make_copyable", (PyCFunction) copyable, METH_VARARGS},
{NULL, NULL} /* sentinel */
+
};
#define AddSym(d, n, f, v) {PyObject *o = f(v); PyDict_SetItemString(d, n, o); Py_DECREF(o);}
@@ -625,6 +790,15 @@ PyMODINIT_FUNC
init_testcapi(void)
{
PyObject *m;
+ PyObject *copy_module;
+
+
+ copy_module = PyImport_ImportModule("copy");
+ if(!copy_module)
+ return;
+ _copy_deepcopy = PyObject_GetAttrString(copy_module, "deepcopy");
+ Py_DECREF(copy_module);
+ Copyable_Type.ob_type = &PyType_Type;
m = Py_InitModule("_testcapi", TestMethods);