Commit e32b4224 authored by Georg Brandl's avatar Georg Brandl

Patch #15916658: implement the __dir__() special function lookup in PyObject_Dir.

parent af334387
...@@ -274,21 +274,34 @@ class C: ...@@ -274,21 +274,34 @@ class C:
\end{funcdesc} \end{funcdesc}
\begin{funcdesc}{dir}{\optional{object}} \begin{funcdesc}{dir}{\optional{object}}
Without arguments, return the list of names in the current local Without arguments, return the list of names in the current local scope. With
symbol table. With an argument, attempts to return a list of valid an argument, attempt to return a list of valid attributes for that object.
attributes for that object. This information is gleaned from the
object's \member{__dict__} attribute, if defined, and from the class If the object has a method named \method{__dir__()}, this method will be
or type object. The list is not necessarily complete. called and must return the list of attributes. This allows objects that
If the object is a module object, the list contains the names of the implement a custom \function{__getattr__()} or \function{__getattribute__()}
module's attributes. function to customize the way \function{dir()} reports their attributes.
If the object is a type or class object,
the list contains the names of its attributes, If the object does not provide \method{__dir__()}, the function tries its best
and recursively of the attributes of its bases. to gather information from the object's \member{__dict__} attribute, if
Otherwise, the list contains the object's attributes' names, defined, and from its type object. The resulting list is not necessarily
the names of its class's attributes, complete, and may be inaccurate when the object has a custom
and recursively of the attributes of its class's base classes. \function{__getattr__()}.
The resulting list is sorted alphabetically.
For example: The default \function{dir()} mechanism behaves differently with different
types of objects, as it attempts to produce the most relevant, rather than
complete, information:
\begin{itemize}
\item If the object is a module object, the list contains the names of the
module's attributes.
\item If the object is a type or class object, the list contains the names of
its attributes, and recursively of the attributes of its bases.
\item Otherwise, the list contains the object's attributes' names, the names
of its class's attributes, and recursively of the attributes of its class's
base classes.
\end{itemize}
The resulting list is sorted alphabetically. For example:
\begin{verbatim} \begin{verbatim}
>>> import struct >>> import struct
...@@ -296,13 +309,19 @@ class C: ...@@ -296,13 +309,19 @@ class C:
['__builtins__', '__doc__', '__name__', 'struct'] ['__builtins__', '__doc__', '__name__', 'struct']
>>> dir(struct) >>> dir(struct)
['__doc__', '__name__', 'calcsize', 'error', 'pack', 'unpack'] ['__doc__', '__name__', 'calcsize', 'error', 'pack', 'unpack']
>>> class Foo(object):
... def __dir__(self):
... return ["kan", "ga", "roo"]
...
>>> f = Foo()
>>> dir(f)
['ga', 'kan', 'roo']
\end{verbatim} \end{verbatim}
\note{Because \function{dir()} is supplied primarily as a convenience \note{Because \function{dir()} is supplied primarily as a convenience for use
for use at an interactive prompt, at an interactive prompt, it tries to supply an interesting set of names
it tries to supply an interesting set of names more than it tries to more than it tries to supply a rigorously or consistently defined set of
supply a rigorously or consistently defined set of names, names, and its detailed behavior may change across releases.}
and its detailed behavior may change across releases.}
\end{funcdesc} \end{funcdesc}
\begin{funcdesc}{divmod}{a, b} \begin{funcdesc}{divmod}{a, b}
......
...@@ -223,12 +223,67 @@ class BuiltinTest(unittest.TestCase): ...@@ -223,12 +223,67 @@ class BuiltinTest(unittest.TestCase):
self.assertRaises(TypeError, delattr) self.assertRaises(TypeError, delattr)
def test_dir(self): def test_dir(self):
x = 1 # dir(wrong number of arguments)
self.assert_('x' in dir())
import sys
self.assert_('modules' in dir(sys))
self.assertRaises(TypeError, dir, 42, 42) self.assertRaises(TypeError, dir, 42, 42)
# dir() - local scope
local_var = 1
self.assert_('local_var' in dir())
# dir(module)
import sys
self.assert_('exit' in dir(sys))
# dir(module_with_invalid__dict__)
import types
class Foo(types.ModuleType):
__dict__ = 8
f = Foo("foo")
self.assertRaises(TypeError, dir, f)
# dir(type)
self.assert_("strip" in dir(str))
self.assert_("__mro__" not in dir(str))
# dir(obj)
class Foo(object):
def __init__(self):
self.x = 7
self.y = 8
self.z = 9
f = Foo()
self.assert_("y" in dir(f))
# dir(obj_no__dict__)
class Foo(object):
__slots__ = []
f = Foo()
self.assert_("__repr__" in dir(f))
# dir(obj_no__class__with__dict__)
# (an ugly trick to cause getattr(f, "__class__") to fail)
class Foo(object):
__slots__ = ["__class__", "__dict__"]
def __init__(self):
self.bar = "wow"
f = Foo()
self.assert_("__repr__" not in dir(f))
self.assert_("bar" in dir(f))
# dir(obj_using __dir__)
class Foo(object):
def __dir__(self):
return ["kan", "ga", "roo"]
f = Foo()
self.assert_(dir(f) == ["ga", "kan", "roo"])
# dir(obj__dir__not_list)
class Foo(object):
def __dir__(self):
return 7
f = Foo()
self.assertRaises(TypeError, dir, f)
def test_divmod(self): def test_divmod(self):
self.assertEqual(divmod(12, 7), (1, 5)) self.assertEqual(divmod(12, 7), (1, 5))
self.assertEqual(divmod(-12, 7), (-2, 2)) self.assertEqual(divmod(-12, 7), (-2, 2))
......
...@@ -28,11 +28,15 @@ TO DO ...@@ -28,11 +28,15 @@ TO DO
Core and Builtins Core and Builtins
----------------- -----------------
- Removing indexing/slicing on BaseException. - The dir() function has been extended to call the __dir__() method on
its argument, if it exists. If not, it will work like before. This allows
customizing the output of dir() in the presence of a __getattr__().
- Remove the exceptions module, all the exceptions are already builtin. - Removed indexing/slicing on BaseException.
- input() becomes raw_input(): the name input() now implements the - Removed the exceptions module, all the exceptions are already builtin.
- input() became raw_input(): the name input() now implements the
functionality formerly known as raw_input(); the name raw_input() functionality formerly known as raw_input(); the name raw_input()
is no longer defined. is no longer defined.
......
...@@ -1284,6 +1284,8 @@ PyCallable_Check(PyObject *x) ...@@ -1284,6 +1284,8 @@ PyCallable_Check(PyObject *x)
return x->ob_type->tp_call != NULL; return x->ob_type->tp_call != NULL;
} }
/* ------------------------- PyObject_Dir() helpers ------------------------- */
/* Helper for PyObject_Dir. /* Helper for PyObject_Dir.
Merge the __dict__ of aclass into dict, and recursively also all Merge the __dict__ of aclass into dict, and recursively also all
the __dict__s of aclass's base classes. The order of merging isn't the __dict__s of aclass's base classes. The order of merging isn't
...@@ -1343,158 +1345,174 @@ merge_class_dict(PyObject* dict, PyObject* aclass) ...@@ -1343,158 +1345,174 @@ merge_class_dict(PyObject* dict, PyObject* aclass)
return 0; return 0;
} }
/* Helper for PyObject_Dir. /* Helper for PyObject_Dir without arguments: returns the local scope. */
If obj has an attr named attrname that's a list, merge its string static PyObject *
elements into keys of dict. _dir_locals()
Return 0 on success, -1 on error. Errors due to not finding the attr, {
or the attr not being a list, are suppressed. PyObject *locals = PyEval_GetLocals();
*/
static int if (locals == NULL) {
merge_list_attr(PyObject* dict, PyObject* obj, const char *attrname) PyErr_SetString(PyExc_SystemError, "frame does not exist");
return NULL;
}
/* the locals don't need to be DECREF'd */
return PyMapping_Keys(locals);
}
/* Helper for PyObject_Dir of type objects: returns __dict__ and __bases__.
We deliberately don't suck up its __class__, as methods belonging to the
metaclass would probably be more confusing than helpful.
*/
static PyObject *
_specialized_dir_type(PyObject *obj)
{ {
PyObject *list; PyObject *result = NULL;
int result = 0; PyObject *dict = PyDict_New();
assert(PyDict_Check(dict)); if (dict != NULL && merge_class_dict(dict, obj) == 0)
assert(obj); result = PyDict_Keys(dict);
assert(attrname);
list = PyObject_GetAttrString(obj, attrname); Py_XDECREF(dict);
if (list == NULL) return result;
PyErr_Clear(); }
else if (PyList_Check(list)) { /* Helper for PyObject_Dir of module objects: returns the module's __dict__. */
int i; static PyObject *
for (i = 0; i < PyList_GET_SIZE(list); ++i) { _specialized_dir_module(PyObject *obj)
PyObject *item = PyList_GET_ITEM(list, i); {
if (PyString_Check(item)) { PyObject *result = NULL;
result = PyDict_SetItem(dict, item, Py_None); PyObject *dict = PyObject_GetAttrString(obj, "__dict__");
if (result < 0)
break; if (dict != NULL) {
} if (PyDict_Check(dict))
result = PyDict_Keys(dict);
else {
PyErr_Format(PyExc_TypeError,
"%.200s.__dict__ is not a dictionary",
PyModule_GetName(obj));
} }
} }
Py_XDECREF(list); Py_XDECREF(dict);
return result; return result;
} }
/* Like __builtin__.dir(arg). See bltinmodule.c's builtin_dir for the /* Helper for PyObject_Dir of generic objects: returns __dict__, __class__,
docstring, which should be kept in synch with this implementation. */ and recursively up the __class__.__bases__ chain.
*/
PyObject * static PyObject *
PyObject_Dir(PyObject *arg) _generic_dir(PyObject *obj)
{ {
/* Set exactly one of these non-NULL before the end. */ PyObject *result = NULL;
PyObject *result = NULL; /* result list */ PyObject *dict = NULL;
PyObject *masterdict = NULL; /* result is masterdict.keys() */ PyObject *itsclass = NULL;
/* If NULL arg, return the locals. */ /* Get __dict__ (which may or may not be a real dict...) */
if (arg == NULL) { dict = PyObject_GetAttrString(obj, "__dict__");
PyObject *locals = PyEval_GetLocals(); if (dict == NULL) {
if (locals == NULL) PyErr_Clear();
goto error; dict = PyDict_New();
result = PyMapping_Keys(locals);
if (result == NULL)
goto error;
} }
else if (!PyDict_Check(dict)) {
/* Elif this is some form of module, we only want its dict. */ Py_DECREF(dict);
else if (PyModule_Check(arg)) { dict = PyDict_New();
masterdict = PyObject_GetAttrString(arg, "__dict__");
if (masterdict == NULL)
goto error;
if (!PyDict_Check(masterdict)) {
PyErr_SetString(PyExc_TypeError,
"module.__dict__ is not a dictionary");
goto error;
}
} }
else {
/* Elif some form of type or class, grab its dict and its bases. /* Copy __dict__ to avoid mutating it. */
We deliberately don't suck up its __class__, as methods belonging PyObject *temp = PyDict_Copy(dict);
to the metaclass would probably be more confusing than helpful. */ Py_DECREF(dict);
else if (PyType_Check(arg)) { dict = temp;
masterdict = PyDict_New();
if (masterdict == NULL)
goto error;
if (merge_class_dict(masterdict, arg) < 0)
goto error;
} }
/* Else look at its dict, and the attrs reachable from its class. */ if (dict == NULL)
goto error;
/* Merge in attrs reachable from its class. */
itsclass = PyObject_GetAttrString(obj, "__class__");
if (itsclass == NULL)
/* XXX(tomer): Perhaps fall back to obj->ob_type if no
__class__ exists? */
PyErr_Clear();
else { else {
PyObject *itsclass; if (merge_class_dict(dict, itsclass) != 0)
/* Create a dict to start with. CAUTION: Not everything
responding to __dict__ returns a dict! */
masterdict = PyObject_GetAttrString(arg, "__dict__");
if (masterdict == NULL) {
PyErr_Clear();
masterdict = PyDict_New();
}
else if (!PyDict_Check(masterdict)) {
Py_DECREF(masterdict);
masterdict = PyDict_New();
}
else {
/* The object may have returned a reference to its
dict, so copy it to avoid mutating it. */
PyObject *temp = PyDict_Copy(masterdict);
Py_DECREF(masterdict);
masterdict = temp;
}
if (masterdict == NULL)
goto error; goto error;
}
/* Merge in __members__ and __methods__ (if any). result = PyDict_Keys(dict);
XXX Would like this to go away someday; for now, it's /* fall through */
XXX needed to get at im_self etc of method objects. */ error:
if (merge_list_attr(masterdict, arg, "__members__") < 0) Py_XDECREF(itsclass);
goto error; Py_XDECREF(dict);
if (merge_list_attr(masterdict, arg, "__methods__") < 0) return result;
goto error; }
/* Merge in attrs reachable from its class. /* Helper for PyObject_Dir: object introspection.
CAUTION: Not all objects have a __class__ attr. */ This calls one of the above specialized versions if no __dir__ method
itsclass = PyObject_GetAttrString(arg, "__class__"); exists. */
if (itsclass == NULL) static PyObject *
PyErr_Clear(); _dir_object(PyObject *obj)
else { {
int status = merge_class_dict(masterdict, itsclass); PyObject * result = NULL;
Py_DECREF(itsclass); PyObject * dirfunc = PyObject_GetAttrString((PyObject*)obj->ob_type,
if (status < 0) "__dir__");
goto error;
}
}
assert((result == NULL) ^ (masterdict == NULL)); assert(obj);
if (masterdict != NULL) { if (dirfunc == NULL) {
/* The result comes from its keys. */ /* use default implementation */
assert(result == NULL); PyErr_Clear();
result = PyDict_Keys(masterdict); if (PyModule_Check(obj))
if (result == NULL) result = _specialized_dir_module(obj);
goto error; else if (PyType_Check(obj))
result = _specialized_dir_type(obj);
else
result = _generic_dir(obj);
} }
else {
/* use __dir__ */
result = PyObject_CallFunctionObjArgs(dirfunc, obj, NULL);
Py_DECREF(dirfunc);
if (result == NULL)
return NULL;
assert(result); /* result must be a list */
if (!PyList_Check(result)) { /* XXX(gbrandl): could also check if all items are strings */
PyErr_Format(PyExc_TypeError, if (!PyList_Check(result)) {
"Expected keys() to be a list, not '%.200s'", PyErr_Format(PyExc_TypeError,
result->ob_type->tp_name); "__dir__() must return a list, not %.200s",
goto error; result->ob_type->tp_name);
Py_DECREF(result);
result = NULL;
}
} }
if (PyList_Sort(result) != 0)
goto error; return result;
}
/* Implementation of dir() -- if obj is NULL, returns the names in the current
(local) scope. Otherwise, performs introspection of the object: returns a
sorted list of attribute names (supposedly) accessible from the object
*/
PyObject *
PyObject_Dir(PyObject *obj)
{
PyObject * result;
if (obj == NULL)
/* no object -- introspect the locals */
result = _dir_locals();
else else
goto normal_return; /* object -- introspect the object */
result = _dir_object(obj);
error: assert(result == NULL || PyList_Check(result));
Py_XDECREF(result);
result = NULL; if (result != NULL && PyList_Sort(result) != 0) {
/* fall through */ /* sorting the list failed */
normal_return: Py_DECREF(result);
Py_XDECREF(masterdict); result = NULL;
}
return result; return result;
} }
......
...@@ -427,15 +427,16 @@ builtin_dir(PyObject *self, PyObject *args) ...@@ -427,15 +427,16 @@ builtin_dir(PyObject *self, PyObject *args)
PyDoc_STRVAR(dir_doc, PyDoc_STRVAR(dir_doc,
"dir([object]) -> list of strings\n" "dir([object]) -> list of strings\n"
"\n" "\n"
"Return an alphabetized list of names comprising (some of) the attributes\n" "If called without an argument, return the names in the current scope.\n"
"of the given object, and of attributes reachable from it:\n" "Else, return an alphabetized list of names comprising (some of) the attributes\n"
"\n" "of the given object, and of attributes reachable from it.\n"
"No argument: the names in the current scope.\n" "If the object supplies a method named __dir__, it will be used; otherwise\n"
"Module object: the module attributes.\n" "the default dir() logic is used and returns:\n"
"Type or class object: its attributes, and recursively the attributes of\n" " for a module object: the module's attributes.\n"
" its bases.\n" " for a class object: its attributes, and recursively the attributes\n"
"Otherwise: its attributes, its class's attributes, and recursively the\n" " of its bases.\n"
" attributes of its class's base classes."); " for an other object: its attributes, its class's attributes, and\n"
" recursively the attributes of its class's base classes.");
static PyObject * static PyObject *
builtin_divmod(PyObject *self, PyObject *args) builtin_divmod(PyObject *self, PyObject *args)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment