Backport from Py3k branch:

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

Had to change a few bits of the patch because classobjs and __methods__ are still
in Py2.6.
This commit is contained in:
Georg Brandl 2007-03-12 13:17:36 +00:00
parent 2681beb23e
commit 871f1bc601
5 changed files with 292 additions and 140 deletions

View file

@ -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:
['__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}

View file

@ -259,12 +259,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))

View file

@ -12,6 +12,10 @@ What's New in Python 2.6 alpha 1?
Core and builtins Core and builtins
----------------- -----------------
- 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__().
- Patch #1675981: remove unreachable code from ``type.__new__()`` method. - Patch #1675981: remove unreachable code from ``type.__new__()`` method.
- Patch #1491866: change the complex() constructor to allow parthensized - Patch #1491866: change the complex() constructor to allow parthensized

View file

@ -1566,6 +1566,8 @@ PyCallable_Check(PyObject *x)
} }
} }
/* ------------------------- 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
@ -1662,121 +1664,192 @@ merge_list_attr(PyObject* dict, PyObject* obj, const char *attrname)
return result; return result;
} }
/* Like __builtin__.dir(arg). See bltinmodule.c's builtin_dir for the /* Helper for PyObject_Dir without arguments: returns the local scope. */
docstring, which should be kept in synch with this implementation. */ static PyObject *
_dir_locals()
PyObject *
PyObject_Dir(PyObject *arg)
{ {
/* Set exactly one of these non-NULL before the end. */ PyObject *names;
PyObject *result = NULL; /* result list */ PyObject *locals = PyEval_GetLocals();
PyObject *masterdict = NULL; /* result is masterdict.keys() */
/* If NULL arg, return the locals. */ if (locals == NULL) {
if (arg == NULL) { PyErr_SetString(PyExc_SystemError, "frame does not exist");
PyObject *locals = PyEval_GetLocals(); return NULL;
if (locals == NULL)
goto error;
result = PyMapping_Keys(locals);
if (result == NULL)
goto error;
} }
/* Elif this is some form of module, we only want its dict. */ names = PyMapping_Keys(locals);
else if (PyModule_Check(arg)) { if (!names)
masterdict = PyObject_GetAttrString(arg, "__dict__"); return NULL;
if (masterdict == NULL) if (!PyList_Check(names)) {
goto error;
if (!PyDict_Check(masterdict)) {
PyErr_SetString(PyExc_TypeError,
"module.__dict__ is not a dictionary");
goto error;
}
}
/* Elif some form of type or class, grab its dict and its bases.
We deliberately don't suck up its __class__, as methods belonging
to the metaclass would probably be more confusing than helpful. */
else if (PyType_Check(arg) || PyClass_Check(arg)) {
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. */
else {
PyObject *itsclass;
/* 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;
/* Merge in __members__ and __methods__ (if any).
XXX Would like this to go away someday; for now, it's
XXX needed to get at im_self etc of method objects. */
if (merge_list_attr(masterdict, arg, "__members__") < 0)
goto error;
if (merge_list_attr(masterdict, arg, "__methods__") < 0)
goto error;
/* Merge in attrs reachable from its class.
CAUTION: Not all objects have a __class__ attr. */
itsclass = PyObject_GetAttrString(arg, "__class__");
if (itsclass == NULL)
PyErr_Clear();
else {
int status = merge_class_dict(masterdict, itsclass);
Py_DECREF(itsclass);
if (status < 0)
goto error;
}
}
assert((result == NULL) ^ (masterdict == NULL));
if (masterdict != NULL) {
/* The result comes from its keys. */
assert(result == NULL);
result = PyDict_Keys(masterdict);
if (result == NULL)
goto error;
}
assert(result);
if (!PyList_Check(result)) {
PyErr_Format(PyExc_TypeError, PyErr_Format(PyExc_TypeError,
"Expected keys() to be a list, not '%.200s'", "dir(): expected keys() of locals to be a list, "
result->ob_type->tp_name); "not '%.200s'", names->ob_type->tp_name);
goto error; Py_DECREF(names);
return NULL;
} }
if (PyList_Sort(result) != 0) /* the locals don't need to be DECREF'd */
goto error; return names;
else }
goto normal_return;
error: /* Helper for PyObject_Dir of type objects: returns __dict__ and __bases__.
Py_XDECREF(result); We deliberately don't suck up its __class__, as methods belonging to the
result = NULL; metaclass would probably be more confusing than helpful.
*/
static PyObject *
_specialized_dir_type(PyObject *obj)
{
PyObject *result = NULL;
PyObject *dict = PyDict_New();
if (dict != NULL && merge_class_dict(dict, obj) == 0)
result = PyDict_Keys(dict);
Py_XDECREF(dict);
return result;
}
/* Helper for PyObject_Dir of module objects: returns the module's __dict__. */
static PyObject *
_specialized_dir_module(PyObject *obj)
{
PyObject *result = NULL;
PyObject *dict = PyObject_GetAttrString(obj, "__dict__");
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(dict);
return result;
}
/* Helper for PyObject_Dir of generic objects: returns __dict__, __class__,
and recursively up the __class__.__bases__ chain.
*/
static PyObject *
_generic_dir(PyObject *obj)
{
PyObject *result = NULL;
PyObject *dict = NULL;
PyObject *itsclass = NULL;
/* Get __dict__ (which may or may not be a real dict...) */
dict = PyObject_GetAttrString(obj, "__dict__");
if (dict == NULL) {
PyErr_Clear();
dict = PyDict_New();
}
else if (!PyDict_Check(dict)) {
Py_DECREF(dict);
dict = PyDict_New();
}
else {
/* Copy __dict__ to avoid mutating it. */
PyObject *temp = PyDict_Copy(dict);
Py_DECREF(dict);
dict = temp;
}
if (dict == NULL)
goto error;
/* Merge in __members__ and __methods__ (if any).
* This is removed in Python 3000. */
if (merge_list_attr(dict, obj, "__members__") < 0)
goto error;
if (merge_list_attr(dict, obj, "__methods__") < 0)
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 {
if (merge_class_dict(dict, itsclass) != 0)
goto error;
}
result = PyDict_Keys(dict);
/* fall through */ /* fall through */
normal_return: error:
Py_XDECREF(masterdict); Py_XDECREF(itsclass);
Py_XDECREF(dict);
return result;
}
/* Helper for PyObject_Dir: object introspection.
This calls one of the above specialized versions if no __dir__ method
exists. */
static PyObject *
_dir_object(PyObject *obj)
{
PyObject * result = NULL;
PyObject * dirfunc = PyObject_GetAttrString((PyObject*)obj->ob_type,
"__dir__");
assert(obj);
if (dirfunc == NULL) {
/* use default implementation */
PyErr_Clear();
if (PyModule_Check(obj))
result = _specialized_dir_module(obj);
else if (PyType_Check(obj) || PyClass_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;
/* result must be a list */
/* XXX(gbrandl): could also check if all items are strings */
if (!PyList_Check(result)) {
PyErr_Format(PyExc_TypeError,
"__dir__() must return a list, not %.200s",
result->ob_type->tp_name);
Py_DECREF(result);
result = NULL;
}
}
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
/* object -- introspect the object */
result = _dir_object(obj);
assert(result == NULL || PyList_Check(result));
if (result != NULL && PyList_Sort(result) != 0) {
/* sorting the list failed */
Py_DECREF(result);
result = NULL;
}
return result; return result;
} }

View file

@ -495,15 +495,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)