[Python-3000-checkins] r54265 - in python/branches/p3yk: Doc/lib/libfuncs.tex Lib/test/test_builtin.py Misc/NEWS Objects/object.c Python/bltinmodule.c

georg.brandl python-3000-checkins at python.org
Sat Mar 10 23:13:29 CET 2007


Author: georg.brandl
Date: Sat Mar 10 23:13:27 2007
New Revision: 54265

Modified:
   python/branches/p3yk/Doc/lib/libfuncs.tex
   python/branches/p3yk/Lib/test/test_builtin.py
   python/branches/p3yk/Misc/NEWS
   python/branches/p3yk/Objects/object.c
   python/branches/p3yk/Python/bltinmodule.c
Log:
Patch #1591665: implement the __dir__() special function lookup in PyObject_Dir.


Modified: python/branches/p3yk/Doc/lib/libfuncs.tex
==============================================================================
--- python/branches/p3yk/Doc/lib/libfuncs.tex	(original)
+++ python/branches/p3yk/Doc/lib/libfuncs.tex	Sat Mar 10 23:13:27 2007
@@ -274,21 +274,34 @@
 \end{funcdesc}
 
 \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.
-  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.
-  The resulting list is sorted alphabetically.
-  For example:
+  Without arguments, return the list of names in the current local scope.  With
+  an argument, attempt to return a list of valid attributes for that object.
+
+  If the object has a method named \method{__dir__()}, this method will be
+  called and must return the list of attributes. This allows objects that
+  implement a custom \function{__getattr__()} or \function{__getattribute__()}
+  function to customize the way \function{dir()} reports their attributes.
+
+  If the object does not provide \method{__dir__()}, the function tries its best
+  to gather information from the object's \member{__dict__} attribute, if
+  defined, and from its 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:
+  \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}
 >>> import struct
@@ -296,13 +309,19 @@
 ['__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
-  for use at an interactive prompt,
-  it tries to supply an interesting set of names more than it tries to
-  supply a rigorously or consistently defined set of names,
-  and its detailed behavior may change across releases.}
+  \note{Because \function{dir()} is supplied primarily as a convenience for use
+    at an interactive prompt, it tries to supply an interesting set of names
+    more than it tries to supply a rigorously or consistently defined set of
+    names, and its detailed behavior may change across releases.}
 \end{funcdesc}
 
 \begin{funcdesc}{divmod}{a, b}

Modified: python/branches/p3yk/Lib/test/test_builtin.py
==============================================================================
--- python/branches/p3yk/Lib/test/test_builtin.py	(original)
+++ python/branches/p3yk/Lib/test/test_builtin.py	Sat Mar 10 23:13:27 2007
@@ -223,12 +223,67 @@
         self.assertRaises(TypeError, delattr)
 
     def test_dir(self):
-        x = 1
-        self.assert_('x' in dir())
-        import sys
-        self.assert_('modules' in dir(sys))
+        # 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_('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):
         self.assertEqual(divmod(12, 7), (1, 5))
         self.assertEqual(divmod(-12, 7), (-2, 2))

Modified: python/branches/p3yk/Misc/NEWS
==============================================================================
--- python/branches/p3yk/Misc/NEWS	(original)
+++ python/branches/p3yk/Misc/NEWS	Sat Mar 10 23:13:27 2007
@@ -28,11 +28,15 @@
 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()
   is no longer defined.
 

Modified: python/branches/p3yk/Objects/object.c
==============================================================================
--- python/branches/p3yk/Objects/object.c	(original)
+++ python/branches/p3yk/Objects/object.c	Sat Mar 10 23:13:27 2007
@@ -1284,6 +1284,8 @@
 	return x->ob_type->tp_call != NULL;
 }
 
+/* ------------------------- PyObject_Dir() helpers ------------------------- */
+
 /* Helper for PyObject_Dir.
    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
@@ -1343,158 +1345,174 @@
 	return 0;
 }
 
-/* Helper for PyObject_Dir.
-   If obj has an attr named attrname that's a list, merge its string
-   elements into keys of dict.
-   Return 0 on success, -1 on error.  Errors due to not finding the attr,
-   or the attr not being a list, are suppressed.
-*/
+/* Helper for PyObject_Dir without arguments: returns the local scope. */
+static PyObject *
+_dir_locals()
+{
+	PyObject *locals = PyEval_GetLocals();
 
-static int
-merge_list_attr(PyObject* dict, PyObject* obj, const char *attrname)
+	if (locals == NULL) {
+		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;
-	int result = 0;
+	PyObject *result = NULL;
+	PyObject *dict = PyDict_New();
 
-	assert(PyDict_Check(dict));
-	assert(obj);
-	assert(attrname);
+	if (dict != NULL && merge_class_dict(dict, obj) == 0)
+		result = PyDict_Keys(dict);
 
-	list = PyObject_GetAttrString(obj, attrname);
-	if (list == NULL)
-		PyErr_Clear();
+	Py_XDECREF(dict);
+	return result;
+}
 
-	else if (PyList_Check(list)) {
-		int i;
-		for (i = 0; i < PyList_GET_SIZE(list); ++i) {
-			PyObject *item = PyList_GET_ITEM(list, i);
-			if (PyString_Check(item)) {
-				result = PyDict_SetItem(dict, item, Py_None);
-				if (result < 0)
-					break;
-			}
+/* 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(list);
+	Py_XDECREF(dict);
 	return result;
 }
 
-/* Like __builtin__.dir(arg).  See bltinmodule.c's builtin_dir for the
-   docstring, which should be kept in synch with this implementation. */
-
-PyObject *
-PyObject_Dir(PyObject *arg)
+/* Helper for PyObject_Dir of generic objects: returns __dict__, __class__,
+   and recursively up the __class__.__bases__ chain.
+*/
+static PyObject *
+_generic_dir(PyObject *obj)
 {
-	/* Set exactly one of these non-NULL before the end. */
-	PyObject *result = NULL;	/* result list */
-	PyObject *masterdict = NULL;	/* result is masterdict.keys() */
-
-	/* 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;
+	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();
 	}
-
-	/* 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)
-			goto error;
-		if (!PyDict_Check(masterdict)) {
-			PyErr_SetString(PyExc_TypeError,
-					"module.__dict__ is not a dictionary");
-			goto error;
-		}
+	else if (!PyDict_Check(dict)) {
+		Py_DECREF(dict);
+		dict = PyDict_New();
 	}
-
-	/* 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)) {
-		masterdict = PyDict_New();
-		if (masterdict == NULL)
-			goto error;
-		if (merge_class_dict(masterdict, arg) < 0)
-			goto error;
+	else {
+		/* Copy __dict__ to avoid mutating it. */
+		PyObject *temp = PyDict_Copy(dict);
+		Py_DECREF(dict);
+		dict = temp;
 	}
 
-	/* 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 {
-		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)
+		if (merge_class_dict(dict, itsclass) != 0)
 			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;
+	result = PyDict_Keys(dict);
+	/* fall through */
+error:
+	Py_XDECREF(itsclass);
+	Py_XDECREF(dict);
+	return result;
+}
 
-		/* 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;
-		}
-	}
+/* 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((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(obj);
+	if (dirfunc == NULL) {
+		/* use default implementation */
+		PyErr_Clear();
+		if (PyModule_Check(obj))
+			result = _specialized_dir_module(obj);
+		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);
-	if (!PyList_Check(result)) {
-		PyErr_Format(PyExc_TypeError,
-			"Expected keys() to be a list, not '%.200s'",
-			result->ob_type->tp_name);
-		goto error;
+		/* 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;
+		}
 	}
-	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
-		goto normal_return;
+		/* object -- introspect the object */
+		result = _dir_object(obj);
 
-  error:
-	Py_XDECREF(result);
-	result = NULL;
-	/* fall through */
-  normal_return:
-  	Py_XDECREF(masterdict);
+	assert(result == NULL || PyList_Check(result));
+
+	if (result != NULL && PyList_Sort(result) != 0) {
+		/* sorting the list failed */
+		Py_DECREF(result);
+		result = NULL;
+	}
+	
 	return result;
 }
 

Modified: python/branches/p3yk/Python/bltinmodule.c
==============================================================================
--- python/branches/p3yk/Python/bltinmodule.c	(original)
+++ python/branches/p3yk/Python/bltinmodule.c	Sat Mar 10 23:13:27 2007
@@ -427,15 +427,16 @@
 PyDoc_STRVAR(dir_doc,
 "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.");
+"If called without an argument, return the names in the current scope.\n"
+"Else, return an alphabetized list of names comprising (some of) the attributes\n"
+"of the given object, and of attributes reachable from it.\n"
+"If the object supplies a method named __dir__, it will be used; otherwise\n"
+"the default dir() logic is used and returns:\n"
+"  for a module object: the module's attributes.\n"
+"  for a class object:  its attributes, and recursively the attributes\n"
+"    of its bases.\n"
+"  for an other object: 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)


More information about the Python-3000-checkins mailing list