Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit6a00f21

Browse files
author
Rémi Lapeyre
committed
Save kwargs given to exceptions constructor
Fix unpickling bug when exception class expect keyword arguments
1 parent1cffd0e commit6a00f21

File tree

8 files changed

+902
-690
lines changed

8 files changed

+902
-690
lines changed

‎Doc/library/exceptions.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ The following exceptions are used mostly as base classes for other exceptions.
8787
assign a special meaning to the elements of this tuple, while others are
8888
usually called only with a single string giving an error message.
8989

90+
..attribute::kwargs
91+
92+
The dictionnary of keyword arguments given to the exception constructor.
93+
9094
..method::with_traceback(tb)
9195

9296
This method sets *tb* as the new traceback for the exception and returns

‎Include/cpython/pyerrors.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ extern "C" {
1010

1111
/* PyException_HEAD defines the initial segment of every exception class. */
1212
#definePyException_HEAD PyObject_HEAD PyObject *dict;\
13-
PyObject *args; PyObject *traceback;\
13+
PyObject *args; PyObject *kwargs; PyObject *traceback;\
1414
PyObject *context; PyObject *cause;\
1515
char suppress_context;
1616

‎Lib/test/test_exceptions.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,6 +1299,43 @@ def g():
12991299
next(i)
13001300
next(i)
13011301

1302+
deftest_get_kwargs(self):
1303+
self.assertEqual(BaseException().kwargs, {})
1304+
self.assertEqual(NaiveException(x=1).kwargs, {'x':1})
1305+
1306+
deftest_set_kwargs(self):
1307+
b=BaseException()
1308+
b.kwargs= {'x':1}
1309+
self.assertEqual(b.kwargs, {'x':1})
1310+
1311+
b=NaiveException(x=1)
1312+
b.kwargs= {'x':2}
1313+
self.assertEqual(b.kwargs, {'x':2})
1314+
1315+
deftest_del_args_kwargs(self):
1316+
b=BaseException()
1317+
1318+
withself.assertRaisesRegex(TypeError,"args may not be deleted"):
1319+
delb.args
1320+
1321+
withself.assertRaisesRegex(TypeError,"kwargs may not be deleted"):
1322+
delb.kwargs
1323+
1324+
deftest_repr(self):
1325+
classMixedArgsKwargs(Exception):
1326+
def__init__(*args,**kwargs):
1327+
pass
1328+
1329+
self.assertEqual(repr(BaseException()),"BaseException()")
1330+
self.assertEqual(repr(BaseException(1)),"BaseException(1)")
1331+
self.assertEqual(repr(NaiveException(1)),"NaiveException(1)")
1332+
self.assertEqual(repr(NaiveException(x=1)),"NaiveException(x=1)")
1333+
self.assertEqual(repr(MixedArgsKwargs(1,b=2)),"MixedArgsKwargs(1, b=2)")
1334+
1335+
classNoKwargs(Exception):
1336+
def__init__(self,foo,):
1337+
self.args= (foo,)
1338+
13021339

13031340
classImportErrorTests(unittest.TestCase):
13041341

@@ -1376,6 +1413,17 @@ def test_copy_pickle(self):
13761413
self.assertEqual(exc.name,orig.name)
13771414
self.assertEqual(exc.path,orig.path)
13781415

1416+
deftest_pickle_overriden_init(self):
1417+
# Issue #27015
1418+
fromsubprocessimportCalledProcessError
1419+
1420+
forprotoinrange(pickle.HIGHEST_PROTOCOL+1):
1421+
orig=CalledProcessError(returncode=2,cmd='foo')
1422+
exc=pickle.loads(pickle.dumps(orig,proto))
1423+
self.assertEqual(orig.cmd,exc.cmd)
1424+
self.assertEqual(orig.returncode,exc.returncode)
1425+
1426+
13791427

13801428
if__name__=='__main__':
13811429
unittest.main()

‎Lib/test/test_sys.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,13 +1004,13 @@ def inner():
10041004
classC(object):pass
10051005
check(C.__dict__,size('P'))
10061006
# BaseException
1007-
check(BaseException(),size('5Pb'))
1007+
check(BaseException(),size('6Pb'))
10081008
# UnicodeEncodeError
1009-
check(UnicodeEncodeError("","",0,0,""),size('5Pb 2P2nP'))
1009+
check(UnicodeEncodeError("","",0,0,""),size('6Pb 2P2nP'))
10101010
# UnicodeDecodeError
1011-
check(UnicodeDecodeError("",b"",0,0,""),size('5Pb 2P2nP'))
1011+
check(UnicodeDecodeError("",b"",0,0,""),size('6Pb 2P2nP'))
10121012
# UnicodeTranslateError
1013-
check(UnicodeTranslateError("",0,1,""),size('5Pb 2P2nP'))
1013+
check(UnicodeTranslateError("",0,1,""),size('6Pb 2P2nP'))
10141014
# ellipses
10151015
check(Ellipsis,size(''))
10161016
# EncodingMap
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Exceptions now save the keyword arguments given to their constructorin their
2+
`kwargs` attribute. Exceptions with overridden __init__ and using keyword
3+
arguments are now picklable. Patch contributed by Rémi Lapeyre.

‎Objects/exceptions.c

Lines changed: 171 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,30 @@ BaseException_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
4141
returnNULL;
4242
/* the dict is created on the fly in PyObject_GenericSetAttr */
4343
self->dict=NULL;
44+
self->kwargs=NULL;
4445
self->traceback=self->cause=self->context=NULL;
4546
self->suppress_context=0;
4647

4748
if (args) {
4849
self->args=args;
4950
Py_INCREF(args);
50-
return (PyObject*)self;
51+
}else {
52+
self->args=PyTuple_New(2);
53+
if (!self->args) {
54+
Py_DECREF(self);
55+
returnNULL;
56+
}
5157
}
5258

53-
self->args=PyTuple_New(0);
54-
if (!self->args) {
55-
Py_DECREF(self);
56-
returnNULL;
59+
if (kwds) {
60+
self->kwargs=kwds;
61+
Py_INCREF(kwds);
62+
}else {
63+
self->kwargs=PyDict_New();
64+
if (!self->kwargs) {
65+
Py_DECREF(self);
66+
returnNULL;
67+
}
5768
}
5869

5970
return (PyObject*)self;
@@ -76,6 +87,7 @@ BaseException_clear(PyBaseExceptionObject *self)
7687
{
7788
Py_CLEAR(self->dict);
7889
Py_CLEAR(self->args);
90+
Py_CLEAR(self->kwargs);
7991
Py_CLEAR(self->traceback);
8092
Py_CLEAR(self->cause);
8193
Py_CLEAR(self->context);
@@ -95,6 +107,7 @@ BaseException_traverse(PyBaseExceptionObject *self, visitproc visit, void *arg)
95107
{
96108
Py_VISIT(self->dict);
97109
Py_VISIT(self->args);
110+
Py_VISIT(self->kwargs);
98111
Py_VISIT(self->traceback);
99112
Py_VISIT(self->cause);
100113
Py_VISIT(self->context);
@@ -118,21 +131,144 @@ static PyObject *
118131
BaseException_repr(PyBaseExceptionObject*self)
119132
{
120133
constchar*name=_PyType_Name(Py_TYPE(self));
121-
if (PyTuple_GET_SIZE(self->args)==1)
122-
returnPyUnicode_FromFormat("%s(%R)",name,
123-
PyTuple_GET_ITEM(self->args,0));
124-
else
125-
returnPyUnicode_FromFormat("%s%R",name,self->args);
134+
PyObject*separator=NULL;
135+
PyObject*args=NULL;
136+
PyObject*kwargs=NULL;
137+
PyObject*seq=NULL;
138+
PyObject*repr=NULL;
139+
PyObject*item=NULL;
140+
PyObject*items=NULL;
141+
PyObject*it=NULL;
142+
PyObject*key=NULL;
143+
PyObject*value=NULL;
144+
PyObject*result=NULL;
145+
146+
separator=PyUnicode_FromString(", ");
147+
148+
if (PyTuple_Check(self->args)) {
149+
constPy_ssize_tlen=PyTuple_Size(self->args);
150+
seq=PyTuple_New(len);
151+
if (seq==NULL) {
152+
gotofail;
153+
}
154+
for (Py_ssize_ti=0;i<len;i++) {
155+
repr=PyObject_Repr(PyTuple_GET_ITEM(self->args,i));
156+
if (repr==NULL) {
157+
gotofail;
158+
}
159+
PyTuple_SET_ITEM(seq,i,repr);
160+
}
161+
args=PyUnicode_Join(separator,seq);
162+
Py_DECREF(seq);
163+
}
164+
165+
if (PyMapping_Check(self->kwargs)) {
166+
constPy_ssize_tlen=PyMapping_Length(self->kwargs);
167+
if (len==-1) {
168+
gotofail;
169+
}
170+
seq=PyTuple_New(len);
171+
items=PyMapping_Items(self->kwargs);
172+
if (seq==NULL||items==NULL) {
173+
gotofail;
174+
}
175+
it=PyObject_GetIter(items);
176+
if (it==NULL) {
177+
gotofail;
178+
}
179+
Py_ssize_ti=0;
180+
while ((item=PyIter_Next(it))!=NULL) {
181+
if (!PyTuple_Check(item)||PyTuple_GET_SIZE(item)!=2) {
182+
PyErr_SetString(PyExc_ValueError,"items must return 2-tuples");
183+
gotofail;
184+
}
185+
key=PyTuple_GET_ITEM(item,0);
186+
value=PyTuple_GET_ITEM(item,1);
187+
PyTuple_SET_ITEM(seq,i,PyUnicode_FromFormat("%S=%R",key,value));
188+
i++;
189+
Py_DECREF(item);
190+
}
191+
kwargs=PyUnicode_Join(separator,seq);
192+
Py_DECREF(seq);
193+
Py_DECREF(items);
194+
Py_DECREF(it);
195+
}
196+
Py_DECREF(separator);
197+
198+
if (args==NULL&&kwargs==NULL) {
199+
result=PyUnicode_FromFormat("%s()",name,kwargs);
200+
}elseif (kwargs==NULL||PyUnicode_GET_LENGTH(kwargs)==0) {
201+
result=PyUnicode_FromFormat("%s(%S)",name,args);
202+
}elseif (args==NULL||PyUnicode_GET_LENGTH(args)==0) {
203+
result=PyUnicode_FromFormat("%s(%S)",name,kwargs);
204+
}else {
205+
result=PyUnicode_FromFormat("%s(%S, %S)",name,args,kwargs);
206+
}
207+
Py_XDECREF(args);
208+
Py_XDECREF(kwargs);
209+
returnresult;
210+
211+
fail:
212+
Py_XDECREF(separator);
213+
Py_XDECREF(args);
214+
Py_XDECREF(kwargs);
215+
Py_XDECREF(seq);
216+
Py_XDECREF(repr);
217+
Py_XDECREF(item);
218+
Py_XDECREF(items);
219+
Py_XDECREF(it);
220+
Py_XDECREF(key);
221+
Py_XDECREF(value);
222+
returnNULL;
126223
}
127224

128225
/* Pickling support */
129226
staticPyObject*
130227
BaseException_reduce(PyBaseExceptionObject*self,PyObject*Py_UNUSED(ignored))
131228
{
132-
if (self->args&&self->dict)
133-
returnPyTuple_Pack(3,Py_TYPE(self),self->args,self->dict);
134-
else
135-
returnPyTuple_Pack(2,Py_TYPE(self),self->args);
229+
PyObject*functools;
230+
PyObject*partial;
231+
PyObject*constructor;
232+
PyObject*args;
233+
PyObject*result;
234+
PyObject**newargs;
235+
236+
_Py_IDENTIFIER(partial);
237+
functools=PyImport_ImportModule("functools");
238+
if (!functools)
239+
returnNULL;
240+
partial=_PyObject_GetAttrId(functools,&PyId_partial);
241+
Py_DECREF(functools);
242+
if (!partial)
243+
returnNULL;
244+
245+
Py_ssize_tlen=1;
246+
if (PyTuple_Check(self->args)) {
247+
len+=PyTuple_GET_SIZE(self->args);
248+
}
249+
newargs=PyMem_RawMalloc(len*sizeof(PyObject*));
250+
newargs[0]= (PyObject*)Py_TYPE(self);
251+
252+
for (Py_ssize_ti=1;i<len;i++) {
253+
newargs[i]=PyTuple_GetItem(self->args,i-1);
254+
}
255+
constructor=_PyObject_FastCallDict(partial,newargs,len,self->kwargs);
256+
PyMem_RawFree(newargs);
257+
258+
Py_DECREF(partial);
259+
260+
args=PyTuple_New(0);
261+
if (!args) {
262+
returnNULL;
263+
}
264+
if (self->args&&self->dict){
265+
result=PyTuple_Pack(3,constructor,args,self->dict);
266+
}else {
267+
result=PyTuple_Pack(2,constructor,args);
268+
}
269+
Py_DECREF(constructor);
270+
Py_DECREF(args);
271+
returnresult;
136272
}
137273

138274
/*
@@ -206,6 +342,26 @@ BaseException_set_args(PyBaseExceptionObject *self, PyObject *val, void *Py_UNUS
206342
return0;
207343
}
208344

345+
staticPyObject*
346+
BaseException_get_kwargs(PyBaseExceptionObject*self,void*Py_UNUSED(ignored)) {
347+
if (self->kwargs==NULL) {
348+
Py_RETURN_NONE;
349+
}
350+
Py_INCREF(self->kwargs);
351+
returnself->kwargs;
352+
}
353+
354+
staticint
355+
BaseException_set_kwargs(PyBaseExceptionObject*self,PyObject*val,void*Py_UNUSED(ignored)) {
356+
if (val==NULL) {
357+
PyErr_SetString(PyExc_TypeError,"kwargs may not be deleted");
358+
return-1;
359+
}
360+
Py_INCREF(val);
361+
self->kwargs=val;
362+
return0;
363+
}
364+
209365
staticPyObject*
210366
BaseException_get_tb(PyBaseExceptionObject*self,void*Py_UNUSED(ignored))
211367
{
@@ -296,6 +452,7 @@ BaseException_set_cause(PyObject *self, PyObject *arg, void *Py_UNUSED(ignored))
296452
staticPyGetSetDefBaseException_getset[]= {
297453
{"__dict__",PyObject_GenericGetDict,PyObject_GenericSetDict},
298454
{"args", (getter)BaseException_get_args, (setter)BaseException_set_args},
455+
{"kwargs", (getter)BaseException_get_kwargs, (setter)BaseException_set_kwargs},
299456
{"__traceback__", (getter)BaseException_get_tb, (setter)BaseException_set_tb},
300457
{"__context__",BaseException_get_context,
301458
BaseException_set_context,PyDoc_STR("exception context")},

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp