diff options
author | Alex Martelli <aleaxit@gmail.com> | 2005-02-07 12:39:55 (GMT) |
---|---|---|
committer | Alex Martelli <aleaxit@gmail.com> | 2005-02-07 12:39:55 (GMT) |
commit | 4c337993c5afdfd9ebb40ff0847cf6015a875f4f (patch) | |
tree | b41dab2d8307066e1df34d7d4eab210ac1a2f6d1 | |
parent | 339870121926f56294d8ccbca381d5d29c07cac0 (diff) | |
download | cpython-4c337993c5afdfd9ebb40ff0847cf6015a875f4f.zip cpython-4c337993c5afdfd9ebb40ff0847cf6015a875f4f.tar.gz cpython-4c337993c5afdfd9ebb40ff0847cf6015a875f4f.tar.bz2 |
forwardport of 2.3.5 fixes to copy.py
-rw-r--r-- | Lib/copy.py | 22 | ||||
-rw-r--r-- | Lib/test/test_copy.py | 101 | ||||
-rw-r--r-- | Modules/_testcapimodule.c | 176 |
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); |