Index: Doc/lib/libfuncs.tex =================================================================== --- Doc/lib/libfuncs.tex (revision 52633) +++ Doc/lib/libfuncs.tex (working copy) @@ -275,18 +275,31 @@ \begin{funcdesc}{dir}{\optional{object}} Without arguments, return the list of names in the current local - symbol table. With an argument, attempts to return a list of valid - attributes for that object. This information is gleaned from the - object's \member{__dict__} attribute, if defined, and from the class - or type object. The list is not necessarily complete. + scope. With an argument, attempts to return a list of valid attributes + for that object. + + If the object has a special method named \member{__dir__}, it will + be called to return the list of attributes. This allows objects that + implement a custom \function{__getattr__}, to customize the way + \function{dir()} reports their attributes. + + If the object does not provide \member{__dir__}, the function tries its + best to gather information from the object's \member{__dict__} attribute, + if defined, and from the class or type object. The resulting list + is not necessarily complete, and may be inaccurate when the object + has a custom \function{__getattr__}. + + The default \function{dir()} mechanism behaves differently with + different types of objects, as it attempts to produce the most + relevant, rather than complete, information: If the object is a module object, the list contains the names of the module's attributes. - 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. - 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. + 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. + 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. + The resulting list is sorted alphabetically. For example: @@ -296,6 +309,13 @@ ['__builtins__', '__doc__', '__name__', 'struct'] >>> dir(struct) ['__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} \note{Because \function{dir()} is supplied primarily as a convenience Index: Lib/test/test_builtin.py =================================================================== --- Lib/test/test_builtin.py (revision 52633) +++ Lib/test/test_builtin.py (working copy) @@ -259,11 +259,74 @@ self.assertRaises(TypeError, delattr) def test_dir(self): - x = 1 - self.assert_('x' in dir()) + # dir(wrong number of arguments) + self.assertRaises(TypeError, dir, 42, 42) + + # dir() - local scope + local_var = 1 + self.assert_('local_var' in dir()) + + # dir(module) import sys - self.assert_('modules' in dir(sys)) - self.assertRaises(TypeError, dir, 42, 42) + 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_with__methods__) + class Foo(object): + __methods__ = ["kan", "ga", "roo"] + __members__ = ["pi", "ka", "chu"] + f = Foo() + self.assert_("kan" in dir(f)) + self.assert_("chu" 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): self.assertEqual(divmod(12, 7), (1, 5)) Index: Objects/object.c =================================================================== --- Objects/object.c (revision 52633) +++ Objects/object.c (working copy) @@ -1662,121 +1662,169 @@ return result; } -/* Like __builtin__.dir(arg). See bltinmodule.c's builtin_dir for the - docstring, which should be kept in synch with this implementation. */ +/* Helper for PyObject_Dir without arguments: returns the local scope */ +static PyObject * +_dir_locals() +{ + PyObject *locals = PyEval_GetLocals(); + + if (locals == NULL) { + PyErr_SetString(PyExc_SystemError, "frame does not exist"); + return NULL; + } + + /* oh oh! don't try to decref the locals :-) */ + return PyMapping_Keys(locals); +} -PyObject * -PyObject_Dir(PyObject *arg) +/* 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) { - /* Set exactly one of these non-NULL before the end. */ - PyObject *result = NULL; /* result list */ - PyObject *masterdict = NULL; /* result is masterdict.keys() */ + 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; +} - /* If NULL arg, return the locals. */ - if (arg == NULL) { - PyObject *locals = PyEval_GetLocals(); - if (locals == NULL) - goto error; - result = PyMapping_Keys(locals); - if (result == NULL) - goto error; +/* 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; +} - /* Elif this is some form of module, we only want its dict. */ - else if (PyModule_Check(arg)) { - masterdict = PyObject_GetAttrString(arg, "__dict__"); - if (masterdict == NULL) +/* 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__ (would be removed by py3k) */ + 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 + XXX __class__ exists? */ + PyErr_Clear(); + else { + if (merge_class_dict(dict, itsclass) != 0) goto error; - if (!PyDict_Check(masterdict)) { - PyErr_SetString(PyExc_TypeError, - "module.__dict__ is not a dictionary"); - goto error; - } } + + result = PyDict_Keys(dict); + /* fall through */ +error: + Py_XDECREF(itsclass); + Py_XDECREF(dict); + return result; +} - /* 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; +/* helper for PyObject_Dir: object introspection */ +static PyObject * +_dir_object(PyObject *obj) +{ + PyObject * result = NULL; + PyObject * dirfunc = PyObject_GetAttrString((PyObject*)obj->ob_type, "__dir__"); + + 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 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(); + /* use __dir__ */ + result = PyObject_CallFunctionObjArgs(dirfunc, obj, NULL); + Py_DECREF(dirfunc); + + /* must be a list (or NULL) */ + if (result != NULL && !PyList_Check(result)) { + PyErr_Format(PyExc_TypeError, + "__dir__ returned non-list (type %.200s)", + result->ob_type->tp_name); + Py_DECREF(result); + result = NULL; } - 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; - } } + + return result; +} - 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; +/* 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); + + if (result != NULL && PyList_Sort(result) != 0) { + Py_DECREF(result); + result = NULL; } - - assert(result); - if (!PyList_Check(result)) { - PyErr_Format(PyExc_TypeError, - "Expected keys() to be a list, not '%.200s'", - result->ob_type->tp_name); - goto error; - } - if (PyList_Sort(result) != 0) - goto error; - else - goto normal_return; - - error: - Py_XDECREF(result); - result = NULL; - /* fall through */ - normal_return: - Py_XDECREF(masterdict); + return result; } Index: Python/bltinmodule.c =================================================================== --- Python/bltinmodule.c (revision 52633) +++ Python/bltinmodule.c (working copy) @@ -486,7 +486,7 @@ builtin_dir(PyObject *self, PyObject *args) { PyObject *arg = NULL; - + if (!PyArg_UnpackTuple(args, "dir", 0, 1, &arg)) return NULL; return PyObject_Dir(arg); @@ -496,14 +496,15 @@ "dir([object]) -> list of strings\n" "\n" "Return an alphabetized list of names comprising (some of) the attributes\n" -"of the given object, and of attributes reachable from it:\n" -"\n" -"No argument: the names in the current scope.\n" -"Module object: the module attributes.\n" -"Type or class object: its attributes, and recursively the attributes of\n" -" its bases.\n" -"Otherwise: its attributes, its class's attributes, and recursively the\n" -" attributes of its class's base classes."); +"of the given object, and of attributes reachable from it.\n" +"If called without an argument, returns the names of the current scope.\n" +"If the object supplies a method named __dir__, it will be used; otherwise\n" +"the default dir() logic is used:\n" +" For module object: the module's attributes.\n" +" For type or class object: its attributes, and recursively the attributes\n" +" of its bases.\n" +" For all other objects: its attributes, its class's attributes, and\n" +" recursively the attributes of its class's base classes."); static PyObject * builtin_divmod(PyObject *self, PyObject *args)