[Python-checkins] cpython: Fix OSError.__init__ and OSError.__new__ so that each of them can be

antoine.pitrou python-checkins at python.org
Thu Dec 15 14:33:09 CET 2011


http://hg.python.org/cpython/rev/d22c99e77768
changeset:   73981:d22c99e77768
parent:      73979:195bfd4c3ea1
user:        Antoine Pitrou <solipsis at pitrou.net>
date:        Thu Dec 15 14:31:28 2011 +0100
summary:
  Fix OSError.__init__ and OSError.__new__ so that each of them can be
overriden and take additional arguments (followup to issue #12555).

files:
  Lib/test/test_pep3151.py |   60 ++++++-
  Misc/NEWS                |    3 +
  Objects/exceptions.c     |  216 +++++++++++++++++++-------
  3 files changed, 217 insertions(+), 62 deletions(-)


diff --git a/Lib/test/test_pep3151.py b/Lib/test/test_pep3151.py
--- a/Lib/test/test_pep3151.py
+++ b/Lib/test/test_pep3151.py
@@ -12,6 +12,23 @@
 class SubOSError(OSError):
     pass
 
+class SubOSErrorWithInit(OSError):
+    def __init__(self, message, bar):
+        self.bar = bar
+        super().__init__(message)
+
+class SubOSErrorWithNew(OSError):
+    def __new__(cls, message, baz):
+        self = super().__new__(cls, message)
+        self.baz = baz
+        return self
+
+class SubOSErrorCombinedInitFirst(SubOSErrorWithInit, SubOSErrorWithNew):
+    pass
+
+class SubOSErrorCombinedNewFirst(SubOSErrorWithNew, SubOSErrorWithInit):
+    pass
+
 
 class HierarchyTest(unittest.TestCase):
 
@@ -74,11 +91,6 @@
             e = OSError(errcode, "Some message")
             self.assertIs(type(e), OSError)
 
-    def test_OSError_subclass_mapping(self):
-        # When constructing an OSError subclass, errno mapping isn't done
-        e = SubOSError(EEXIST, "Bad file descriptor")
-        self.assertIs(type(e), SubOSError)
-
     def test_try_except(self):
         filename = "some_hopefully_non_existing_file"
 
@@ -144,6 +156,44 @@
     # XXX VMSError not tested
 
 
+class ExplicitSubclassingTest(unittest.TestCase):
+
+    def test_errno_mapping(self):
+        # When constructing an OSError subclass, errno mapping isn't done
+        e = SubOSError(EEXIST, "Bad file descriptor")
+        self.assertIs(type(e), SubOSError)
+
+    def test_init_overriden(self):
+        e = SubOSErrorWithInit("some message", "baz")
+        self.assertEqual(e.bar, "baz")
+        self.assertEqual(e.args, ("some message",))
+
+    def test_init_kwdargs(self):
+        e = SubOSErrorWithInit("some message", bar="baz")
+        self.assertEqual(e.bar, "baz")
+        self.assertEqual(e.args, ("some message",))
+
+    def test_new_overriden(self):
+        e = SubOSErrorWithNew("some message", "baz")
+        self.assertEqual(e.baz, "baz")
+        self.assertEqual(e.args, ("some message",))
+
+    def test_new_kwdargs(self):
+        e = SubOSErrorWithNew("some message", baz="baz")
+        self.assertEqual(e.baz, "baz")
+        self.assertEqual(e.args, ("some message",))
+
+    def test_init_new_overriden(self):
+        e = SubOSErrorCombinedInitFirst("some message", "baz")
+        self.assertEqual(e.bar, "baz")
+        self.assertEqual(e.baz, "baz")
+        self.assertEqual(e.args, ("some message",))
+        e = SubOSErrorCombinedNewFirst("some message", "baz")
+        self.assertEqual(e.bar, "baz")
+        self.assertEqual(e.baz, "baz")
+        self.assertEqual(e.args, ("some message",))
+
+
 def test_main():
     support.run_unittest(__name__)
 
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,9 @@
 Core and Builtins
 -----------------
 
+- Fix OSError.__init__ and OSError.__new__ so that each of them can be
+  overriden and take additional arguments (followup to issue #12555).
+
 - Fix the fix for issue #12149: it was incorrect, although it had the side
   effect of appearing to resolve the issue.  Thanks to Mark Shannon for
   noticing.
diff --git a/Objects/exceptions.c b/Objects/exceptions.c
--- a/Objects/exceptions.c
+++ b/Objects/exceptions.c
@@ -58,7 +58,7 @@
     if (!_PyArg_NoKeywords(Py_TYPE(self)->tp_name, kwds))
         return -1;
 
-    Py_DECREF(self->args);
+    Py_XDECREF(self->args);
     self->args = args;
     Py_INCREF(self->args);
 
@@ -587,37 +587,34 @@
  * when it was supplied.
  */
 
-static PyObject *
-OSError_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+/* This function doesn't cleanup on error, the caller should */
+static int
+oserror_parse_args(PyObject **p_args,
+                   PyObject **myerrno, PyObject **strerror,
+                   PyObject **filename
+#ifdef MS_WINDOWS
+                   , PyObject **winerror
+#endif
+                  )
 {
-    PyOSErrorObject *self = NULL;
     Py_ssize_t nargs;
+    PyObject *args = *p_args;
 
-    PyObject *myerrno = NULL, *strerror = NULL, *filename = NULL;
-    PyObject *subslice = NULL;
-#ifdef MS_WINDOWS
-    PyObject *winerror = NULL;
-    long winerrcode = 0;
-#endif
-
-    if (!_PyArg_NoKeywords(type->tp_name, kwds))
-        return NULL;
-    Py_INCREF(args);
     nargs = PyTuple_GET_SIZE(args);
 
 #ifdef MS_WINDOWS
     if (nargs >= 2 && nargs <= 4) {
         if (!PyArg_UnpackTuple(args, "OSError", 2, 4,
-                               &myerrno, &strerror, &filename, &winerror))
-            goto error;
-        if (winerror && PyLong_Check(winerror)) {
-            long errcode;
+                               myerrno, strerror, filename, winerror))
+            return -1;
+        if (*winerror && PyLong_Check(*winerror)) {
+            long errcode, winerrcode;
             PyObject *newargs;
             Py_ssize_t i;
 
-            winerrcode = PyLong_AsLong(winerror);
+            winerrcode = PyLong_AsLong(*winerror);
             if (winerrcode == -1 && PyErr_Occurred())
-                goto error;
+                return -1;
             /* Set errno to the corresponding POSIX errno (overriding
                first argument).  Windows Socket error codes (>= 10000)
                have the same value as their POSIX counterparts.
@@ -626,59 +623,55 @@
                 errcode = winerror_to_errno(winerrcode);
             else
                 errcode = winerrcode;
-            myerrno = PyLong_FromLong(errcode);
-            if (!myerrno)
-                goto error;
+            *myerrno = PyLong_FromLong(errcode);
+            if (!*myerrno)
+                return -1;
             newargs = PyTuple_New(nargs);
             if (!newargs)
-                goto error;
-            PyTuple_SET_ITEM(newargs, 0, myerrno);
+                return -1;
+            PyTuple_SET_ITEM(newargs, 0, *myerrno);
             for (i = 1; i < nargs; i++) {
                 PyObject *val = PyTuple_GET_ITEM(args, i);
                 Py_INCREF(val);
                 PyTuple_SET_ITEM(newargs, i, val);
             }
             Py_DECREF(args);
-            args = newargs;
+            args = *p_args = newargs;
         }
     }
 #else
     if (nargs >= 2 && nargs <= 3) {
         if (!PyArg_UnpackTuple(args, "OSError", 2, 3,
-                               &myerrno, &strerror, &filename))
-            goto error;
+                               myerrno, strerror, filename))
+            return -1;
     }
 #endif
-    if (myerrno && PyLong_Check(myerrno) &&
-        errnomap && (PyObject *) type == PyExc_OSError) {
-        PyObject *newtype;
-        newtype = PyDict_GetItem(errnomap, myerrno);
-        if (newtype) {
-            assert(PyType_Check(newtype));
-            type = (PyTypeObject *) newtype;
-        }
-        else if (PyErr_Occurred())
-            goto error;
-    }
 
-    self = (PyOSErrorObject *) type->tp_alloc(type, 0);
-    if (!self)
-        goto error;
+    return 0;
+}
 
-    self->dict = NULL;
-    self->traceback = self->cause = self->context = NULL;
-    self->written = -1;
+static int
+oserror_init(PyOSErrorObject *self, PyObject **p_args,
+             PyObject *myerrno, PyObject *strerror,
+             PyObject *filename
+#ifdef MS_WINDOWS
+             , PyObject *winerror
+#endif
+             )
+{
+    PyObject *args = *p_args;
+    Py_ssize_t nargs = PyTuple_GET_SIZE(args);
 
     /* self->filename will remain Py_None otherwise */
     if (filename && filename != Py_None) {
-        if ((PyObject *) type == PyExc_BlockingIOError &&
+        if (Py_TYPE(self) == (PyTypeObject *) PyExc_BlockingIOError &&
             PyNumber_Check(filename)) {
             /* BlockingIOError's 3rd argument can be the number of
              * characters written.
              */
             self->written = PyNumber_AsSsize_t(filename, PyExc_ValueError);
             if (self->written == -1 && PyErr_Occurred())
-                goto error;
+                return -1;
         }
         else {
             Py_INCREF(filename);
@@ -687,20 +680,15 @@
             if (nargs >= 2 && nargs <= 3) {
                 /* filename is removed from the args tuple (for compatibility
                    purposes, see test_exceptions.py) */
-                subslice = PyTuple_GetSlice(args, 0, 2);
+                PyObject *subslice = PyTuple_GetSlice(args, 0, 2);
                 if (!subslice)
-                    goto error;
+                    return -1;
 
                 Py_DECREF(args);  /* replacing args */
-                args = subslice;
+                *p_args = args = subslice;
             }
         }
     }
-
-    /* Steals the reference to args */
-    self->args = args;
-    args = NULL;
-
     Py_XINCREF(myerrno);
     self->myerrno = myerrno;
 
@@ -712,6 +700,90 @@
     self->winerror = winerror;
 #endif
 
+    /* Steals the reference to args */
+    self->args = args;
+    args = NULL;
+
+    return 0;
+}
+
+static PyObject *
+OSError_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
+static int
+OSError_init(PyOSErrorObject *self, PyObject *args, PyObject *kwds);
+
+static int
+oserror_use_init(PyTypeObject *type)
+{
+    /* When __init__ is defined in a OSError subclass, we want any
+       extraneous argument to __new__ to be ignored.  The only reasonable
+       solution, given __new__ takes a variable number of arguments,
+       is to defer arg parsing and initialization to __init__.
+
+       But when __new__ is overriden as well, it should call our __new__
+       with the right arguments.
+
+       (see http://bugs.python.org/issue12555#msg148829 )
+    */
+    if (type->tp_init != (initproc) OSError_init &&
+        type->tp_new == (newfunc) OSError_new) {
+        assert((PyObject *) type != PyExc_OSError);
+        return 1;
+    }
+    return 0;
+}
+
+static PyObject *
+OSError_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+    PyOSErrorObject *self = NULL;
+    PyObject *myerrno = NULL, *strerror = NULL, *filename = NULL;
+#ifdef MS_WINDOWS
+    PyObject *winerror = NULL;
+#endif
+
+    if (!oserror_use_init(type)) {
+        if (!_PyArg_NoKeywords(type->tp_name, kwds))
+            return NULL;
+
+        Py_INCREF(args);
+        if (oserror_parse_args(&args, &myerrno, &strerror, &filename
+#ifdef MS_WINDOWS
+                               , &winerror
+#endif
+            ))
+            goto error;
+
+        if (myerrno && PyLong_Check(myerrno) &&
+            errnomap && (PyObject *) type == PyExc_OSError) {
+            PyObject *newtype;
+            newtype = PyDict_GetItem(errnomap, myerrno);
+            if (newtype) {
+                assert(PyType_Check(newtype));
+                type = (PyTypeObject *) newtype;
+            }
+            else if (PyErr_Occurred())
+                goto error;
+        }
+    }
+
+    self = (PyOSErrorObject *) type->tp_alloc(type, 0);
+    if (!self)
+        goto error;
+
+    self->dict = NULL;
+    self->traceback = self->cause = self->context = NULL;
+    self->written = -1;
+
+    if (!oserror_use_init(type)) {
+        if (oserror_init(self, &args, myerrno, strerror, filename
+#ifdef MS_WINDOWS
+                         , winerror
+#endif
+            ))
+            goto error;
+    }
+
     return (PyObject *) self;
 
 error:
@@ -721,10 +793,40 @@
 }
 
 static int
-OSError_init(PySyntaxErrorObject *self, PyObject *args, PyObject *kwds)
+OSError_init(PyOSErrorObject *self, PyObject *args, PyObject *kwds)
 {
-    /* Everything already done in OSError_new */
+    PyObject *myerrno = NULL, *strerror = NULL, *filename = NULL;
+#ifdef MS_WINDOWS
+    PyObject *winerror = NULL;
+#endif
+
+    if (!oserror_use_init(Py_TYPE(self)))
+        /* Everything already done in OSError_new */
+        return 0;
+
+    if (!_PyArg_NoKeywords(Py_TYPE(self)->tp_name, kwds))
+        return -1;
+
+    Py_INCREF(args);
+    if (oserror_parse_args(&args, &myerrno, &strerror, &filename
+#ifdef MS_WINDOWS
+                           , &winerror
+#endif
+        ))
+        goto error;
+
+    if (oserror_init(self, &args, myerrno, strerror, filename
+#ifdef MS_WINDOWS
+                     , winerror
+#endif
+        ))
+        goto error;
+
     return 0;
+
+error:
+    Py_XDECREF(args);
+    return -1;
 }
 
 static int

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list