Last active
September 6, 2018 21:20
-
-
Save 1st1/fd13f74f43fba0f6cc7b2d19f58772fd to your computer and use it in GitHub Desktop.
Backport PEP 562 module-level __getattr__ to Python 2.7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/Lib/test/bad_getattr.py b/Lib/test/bad_getattr.py | |
new file mode 100644 | |
index 0000000000..b4f790f3bf | |
--- /dev/null | |
+++ b/Lib/test/bad_getattr.py | |
@@ -0,0 +1,3 @@ | |
+x = 1 | |
+ | |
+__getattr__ = "Surprise!" | |
diff --git a/Lib/test/bad_getattr2.py b/Lib/test/bad_getattr2.py | |
new file mode 100644 | |
index 0000000000..21fa74f0ba | |
--- /dev/null | |
+++ b/Lib/test/bad_getattr2.py | |
@@ -0,0 +1,4 @@ | |
+def __getattr__(): | |
+ "Bad one" | |
+ | |
+x = 1 | |
diff --git a/Lib/test/good_getattr.py b/Lib/test/good_getattr.py | |
new file mode 100644 | |
index 0000000000..1b8bb5305a | |
--- /dev/null | |
+++ b/Lib/test/good_getattr.py | |
@@ -0,0 +1,8 @@ | |
+x = 1 | |
+ | |
+def __getattr__(name): | |
+ if name == "yolo" or name == "__warningregistry__": | |
+ raise AttributeError("Deprecated, use whatever instead") | |
+ return "There is {name}".format(name=name) | |
+ | |
+y = 2 | |
diff --git a/Lib/test/test_module.py b/Lib/test/test_module.py | |
index 21eaf3ed72..acab944d1f 100644 | |
--- a/Lib/test/test_module.py | |
+++ b/Lib/test/test_module.py | |
@@ -6,6 +6,26 @@ import sys | |
ModuleType = type(sys) | |
class ModuleTests(unittest.TestCase): | |
+ def test_module_getattr(self): | |
+ import test.good_getattr as gga | |
+ from test.good_getattr import test | |
+ self.assertEqual(test, "There is test") | |
+ self.assertEqual(gga.x, 1) | |
+ self.assertEqual(gga.y, 2) | |
+ with self.assertRaises(AttributeError): | |
+ gga.yolo | |
+ self.assertEqual(gga.whatever, "There is whatever") | |
+ | |
+ def test_module_getattr_errors(self): | |
+ import test.bad_getattr as bga | |
+ from test import bad_getattr2 | |
+ self.assertEqual(bga.x, 1) | |
+ self.assertEqual(bad_getattr2.x, 1) | |
+ with self.assertRaises(TypeError): | |
+ bga.nope | |
+ with self.assertRaises(TypeError): | |
+ bad_getattr2.nope | |
+ | |
def test_uninitialized(self): | |
# An uninitialized module has no __dict__ or __name__, | |
# and __doc__ is None | |
diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c | |
index f2fed30e90..d5c68d1e51 100644 | |
--- a/Objects/moduleobject.c | |
+++ b/Objects/moduleobject.c | |
@@ -202,6 +202,33 @@ module_repr(PyModuleObject *m) | |
return PyString_FromFormat("<module '%s' from '%s'>", name, filename); | |
} | |
+static PyObject* | |
+module_getattro(PyModuleObject *m, PyObject *name) | |
+{ | |
+ PyObject *attr; | |
+ | |
+ attr = PyObject_GenericGetAttr((PyObject *)m, name); | |
+ if (attr || !PyErr_ExceptionMatches(PyExc_AttributeError)) { | |
+ return attr; | |
+ } | |
+ | |
+ PyErr_Clear(); | |
+ if (m->md_dict) { | |
+ PyObject *getattr = PyDict_GetItemString(m->md_dict, "__getattr__"); | |
+ if (getattr) { | |
+ Py_INCREF(getattr); | |
+ attr = PyObject_CallFunctionObjArgs(getattr, name, NULL); | |
+ Py_DECREF(getattr); | |
+ return attr; | |
+ } | |
+ } | |
+ | |
+ PyErr_Format(PyExc_AttributeError, | |
+ "'module' object has no attribute '%.400s'", | |
+ PyString_AS_STRING(name)); | |
+ return NULL; | |
+} | |
+ | |
/* We only need a traverse function, no clear function: If the module | |
is in a cycle, md_dict will be cleared as well, which will break | |
the cycle. */ | |
@@ -235,7 +262,7 @@ PyTypeObject PyModule_Type = { | |
0, /* tp_hash */ | |
0, /* tp_call */ | |
0, /* tp_str */ | |
- PyObject_GenericGetAttr, /* tp_getattro */ | |
+ (getattrofunc)module_getattro, /* tp_getattro */ | |
PyObject_GenericSetAttr, /* tp_setattro */ | |
0, /* tp_as_buffer */ | |
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment