Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4e3363e

Browse files
committed
Warn about creating global variables by __setattr__ that shadow builtin
names. Unfortunately, this is not bulletproof since the module dictionary can be modified directly.
1 parent c4370d9 commit 4e3363e

2 files changed

Lines changed: 72 additions & 1 deletion

File tree

Misc/NEWS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ Core and builtins
4343
instead of going through __getitem__. If __getitem__ access is
4444
preferred, then __iter__ can be overriden.
4545

46+
- Creating an attribute on a module (i.e. a global variable created by
47+
__setattr__) that causes a builtin name to be shadowed now raises a
48+
DeprecationWarning. In future versions of Python the effect may be
49+
undefined (in order to allow for optimization of global and builtin
50+
name lookups).
51+
4652
Extension modules
4753
-----------------
4854

Objects/moduleobject.c

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,71 @@ module_repr(PyModuleObject *m)
198198
return PyString_FromFormat("<module '%s' from '%s'>", name, filename);
199199
}
200200

201+
static PyObject *
202+
find_builtin_names(void)
203+
{
204+
PyObject *builtins, *names, *key, *value;
205+
int pos = 0;
206+
builtins = PyEval_GetBuiltins();
207+
if (builtins == NULL || !PyDict_Check(builtins)) {
208+
PyErr_SetString(PyExc_SystemError, "no builtins dict!");
209+
return NULL;
210+
}
211+
names = PyDict_New();
212+
if (names == NULL)
213+
return NULL;
214+
while (PyDict_Next(builtins, &pos, &key, &value)) {
215+
if (PyString_Check(key) &&
216+
PyString_Size(key) > 0 &&
217+
PyString_AS_STRING(key)[0] != '_') {
218+
if (PyDict_SetItem(names, key, Py_None) < 0) {
219+
Py_DECREF(names);
220+
return NULL;
221+
}
222+
}
223+
}
224+
return names;
225+
}
226+
227+
/* returns 0 or 1 (and -1 on error) */
228+
static int
229+
shadows_builtin(PyObject *globals, PyObject *name)
230+
{
231+
static PyObject *builtin_names = NULL;
232+
if (builtin_names == NULL) {
233+
builtin_names = find_builtin_names();
234+
if (builtin_names == NULL)
235+
return -1;
236+
}
237+
if (!PyString_Check(name))
238+
return 0;
239+
if (PyDict_GetItem(globals, name) == NULL &&
240+
PyDict_GetItem(builtin_names, name) != NULL) {
241+
return 1;
242+
}
243+
else {
244+
return 0;
245+
}
246+
}
247+
248+
static int
249+
module_setattr(PyObject *m, PyObject *name, PyObject *value)
250+
{
251+
PyObject *globals = ((PyModuleObject *)m)->md_dict;
252+
PyObject *builtins = PyEval_GetBuiltins();
253+
if (globals != NULL && globals != builtins) {
254+
int shadows = shadows_builtin(globals, name);
255+
if (shadows == 1) {
256+
if (PyErr_Warn(PyExc_DeprecationWarning,
257+
"assignment shadows builtin") < 0)
258+
return -1;
259+
}
260+
else if (shadows == -1)
261+
return -1;
262+
}
263+
return PyObject_GenericSetAttr(m, name, value);
264+
}
265+
201266
/* We only need a traverse function, no clear function: If the module
202267
is in a cycle, md_dict will be cleared as well, which will break
203268
the cycle. */
@@ -234,7 +299,7 @@ PyTypeObject PyModule_Type = {
234299
0, /* tp_call */
235300
0, /* tp_str */
236301
PyObject_GenericGetAttr, /* tp_getattro */
237-
PyObject_GenericSetAttr, /* tp_setattro */
302+
module_setattr, /* tp_setattro */
238303
0, /* tp_as_buffer */
239304
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
240305
Py_TPFLAGS_BASETYPE, /* tp_flags */

0 commit comments

Comments
 (0)