]> granicus.if.org Git - python/commitdiff
Make dir() wordier (see the new docstring). The new behavior is a mixed
authorTim Peters <tim.peters@gmail.com>
Mon, 3 Sep 2001 05:47:38 +0000 (05:47 +0000)
committerTim Peters <tim.peters@gmail.com>
Mon, 3 Sep 2001 05:47:38 +0000 (05:47 +0000)
bag.  It's clearly wrong for classic classes, at heart because a classic
class doesn't have a __class__ attribute, and I'm unclear on whether
that's feature or bug.  I'll repair this once I find out (in the
meantime, dir() applied to classic classes won't find the base classes,
while dir() applied to a classic-class instance *will* find the base
classes but not *their* base classes).

Please give the new dir() a try and see whether you love it or hate it.
The new dir([]) behavior is something I could come to love.  Here's
something to hate:

>>> class C:
...     pass
...
>>> c = C()
>>> dir(c)
['__doc__', '__module__']
>>>

The idea that an instance has a __doc__ attribute is jarring (of course
it's really c.__class__.__doc__ == C.__doc__; likewise for __module__).

OTOH, the code already has too many special cases, and dir(x) doesn't
have a compelling or clear purpose when x isn't a module.

Lib/test/test_descr.py
Lib/test/test_descrtut.py
Lib/test/test_generators.py
Misc/NEWS
Python/bltinmodule.c

index f6b9e1be1fd0bf10f322d0361c5f0b52b9e8eee7..ee924dc0c928a41f96b9b55e3a59f739e9bd3229 100644 (file)
@@ -172,6 +172,54 @@ def dict_constructor():
     d = dictionary(mapping=Mapping())
     verify(d == Mapping.dict)
 
+def test_dir():
+    if verbose:
+        print "Testing dir() ..."
+    junk = 12
+    verify(dir() == ['junk'])
+    del junk
+
+    # Just make sure these don't blow up!
+    for arg in 2, 2L, 2j, 2e0, [2], "2", u"2", (2,), {2:2}, type, test_dir:
+        dir(arg)
+
+    # Check some details here because classic classes aren't working
+    # reasonably, and I want this to fail (eventually).
+    class C:
+        Cdata = 1
+        def Cmethod(self): pass
+
+    cstuff = ['Cdata', 'Cmethod', '__doc__', '__module__']
+    verify(dir(C) == cstuff)
+
+    c = C()  # c.__doc__ is an odd thing to see here; ditto c.__module__.
+    verify(dir(c) == cstuff)
+
+    c.cdata = 2
+    c.cmethod = lambda self: 0
+    verify(dir(c) == cstuff + ['cdata', 'cmethod'])
+
+    class A(C):
+        Adata = 1
+        def Amethod(self): pass
+    astuff = ['Adata', 'Amethod', '__doc__', '__module__']
+    # This isn't finding C's stuff at all.
+    verify(dir(A) == astuff)
+    # But this is!  It's because a.__class__ exists but A.__class__ doesn't.
+    a = A()
+    verify(dir(a) == astuff[:2] + cstuff)
+
+    # The story for new-style classes is quite different.
+    class C(object):
+        Cdata = 1
+        def Cmethod(self): pass
+    class A(C):
+        Adata = 1
+        def Amethod(self): pass
+    d = dir(A)
+    for expected in 'Cdata', 'Cmethod', 'Adata', 'Amethod':
+        verify(expected in d)
+
 binops = {
     'add': '+',
     'sub': '-',
@@ -1349,6 +1397,7 @@ def all():
     lists()
     dicts()
     dict_constructor()
+    test_dir()
     ints()
     longs()
     floats()
index edb0388ef8c07943173df4ba84f313afe00b599f..121eed52f1f277360d5aa179b674d43c390a5c09 100644 (file)
@@ -97,14 +97,15 @@ just like classic classes:
     >>> a.default = -1000
     >>> print a["noway"]
     -1000
-    >>> print dir(a)
-    ['default']
+    >>> 'default' in dir(a)
+    1
     >>> a.x1 = 100
     >>> a.x2 = 200
     >>> print a.x1
     100
-    >>> print dir(a)
-    ['default', 'x1', 'x2']
+    >>> d = dir(a)
+    >>> 'default' in d and 'x1' in d and 'x2' in d
+    1
     >>> print a.__dict__
     {'default': -1000, 'x2': 200, 'x1': 100}
     >>>
index 947e26f4b6d8e8ea7686ca03a13e5d8ebf706932..0e9d0607399d79100788731983a596f4ea46927f 100644 (file)
@@ -383,14 +383,8 @@ From the Iterators list, about the types of these things.
 >>> i = g()
 >>> type(i)
 <type 'generator'>
-
-XXX dir(object) *generally* doesn't return useful stuff in descr-branch.
->>> dir(i)
-[]
-
-Was hoping to see this instead:
+>>> [s for s in dir(i) if not s.startswith('_')]
 ['gi_frame', 'gi_running', 'next']
-
 >>> print i.next.__doc__
 x.next() -> the next value, or raise StopIteration
 >>> iter(i) is i
index 55e29930c941026ab5c4fed918f1bdcaf75b8d74..a1557f88d47a017e66fdd9d2469cdbde4b844678 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -3,6 +3,23 @@ What's New in Python 2.2a3?
 
 Core
 
+- The builtin dir() now returns more information, and sometimes much
+  more, generally naming all attributes of an object, and all attributes
+  reachable from the object via its class, and from its class's base
+  classes, and so on from them too.  Example:  in 2.2a2, dir([]) returned
+  an empty list.  In 2.2a3,
+
+  >>> dir([])
+  ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__',
+   '__eq__', '__ge__', '__getattr__', '__getitem__', '__getslice__',
+   '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__le__',
+   '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__repr__',
+   '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__str__',
+   'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove',
+   'reverse', 'sort']
+
+  dir(module) continues to return only the module's attributes, though.
+
 - Overflowing operations on plain ints now return a long int rather
   than raising OverflowError.  This is a partial implementation of PEP
   237.  You can use -Wdefault::OverflowWarning to enable a warning for
index 62bf2a7e3a709409527be83e3d5aa181aaebd0f2..d5dc32247fdcdbb09f805dde905549252796492c 100644 (file)
@@ -426,80 +426,161 @@ the effects of any future statements in effect in the code calling\n\
 compile; if absent or zero these statements do influence the compilation,\n\
 in addition to any features explicitly specified.";
 
+/* 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
+   defined, as it's expected that only the final set of dict keys is
+   interesting.
+   Return 0 on success, -1 on error.
+*/
+
+static int
+merge_class_dict(PyObject* dict, PyObject* aclass)
+{
+       PyObject *classdict;
+       PyObject *bases;
+
+       assert(PyDict_Check(dict));
+       /* XXX Class objects fail the PyType_Check check.  Don't
+          XXX know of others. */
+       /* assert(PyType_Check(aclass)); */
+       assert(aclass);
+
+       /* Merge in the type's dict (if any). */
+       classdict = PyObject_GetAttrString(aclass, "__dict__");
+       if (classdict == NULL)
+               PyErr_Clear();
+       else {
+               int status = PyDict_Update(dict, classdict);
+               Py_DECREF(classdict);
+               if (status < 0)
+                       return -1;
+       }
+
+       /* Recursively merge in the base types' (if any) dicts. */
+       bases = PyObject_GetAttrString(aclass, "__bases__");
+       if (bases != NULL) {
+               int i, n;
+               assert(PyTuple_Check(bases));
+               n = PyTuple_GET_SIZE(bases);
+               for (i = 0; i < n; i++) {
+                       PyObject *base = PyTuple_GET_ITEM(bases, i);
+                       if (merge_class_dict(dict, base) < 0) {
+                               Py_DECREF(bases);
+                               return -1;
+                       }
+               }
+               Py_DECREF(bases);
+       }
+       return 0;
+}
 
 static PyObject *
 builtin_dir(PyObject *self, PyObject *args)
 {
-       static char *attrlist[] = {"__members__", "__methods__", NULL};
-       PyObject *v = NULL, *l = NULL, *m = NULL;
-       PyObject *d, *x;
-       int i;
-       char **s;
+       PyObject *arg = NULL;
+       /* Set exactly one of these non-NULL before the end. */
+       PyObject *result = NULL;        /* result list */
+       PyObject *masterdict = NULL;    /* result is masterdict.keys() */
 
-       if (!PyArg_ParseTuple(args, "|O:dir", &v))
+       if (!PyArg_ParseTuple(args, "|O:dir", &arg))
                return NULL;
-       if (v == NULL) {
-               x = PyEval_GetLocals();
-               if (x == NULL)
+
+       /* If no arg, return the locals. */
+       if (arg == NULL) {
+               PyObject *locals = PyEval_GetLocals();
+               if (locals == NULL)
                        goto error;
-               l = PyMapping_Keys(x);
-               if (l == NULL)
+               result = PyMapping_Keys(locals);
+               if (result == NULL)
+                       goto error;
+       }
+
+       /* Elif this is some form of module, we only want its dict. */
+       else if (PyObject_TypeCheck(arg, &PyModule_Type)) {
+               masterdict = PyObject_GetAttrString(arg, "__dict__");
+               if (masterdict == NULL)
+                       goto error;
+       }
+
+       /* Elif some form of type, recurse. */
+       else if (PyType_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 {
-               d = PyObject_GetAttrString(v, "__dict__");
-               if (d == NULL)
+               PyObject *itsclass;
+               /* Create a dict to start with. */
+               masterdict = PyObject_GetAttrString(arg, "__dict__");
+               if (masterdict == NULL) {
                        PyErr_Clear();
-               else {
-                       l = PyMapping_Keys(d);
-                       if (l == NULL)
-                               PyErr_Clear();
-                       Py_DECREF(d);
+                       masterdict = PyDict_New();
+                       if (masterdict == NULL)
+                               goto error;
                }
-               if (l == NULL) {
-                       l = PyList_New(0);
-                       if (l == NULL)
+               else {
+                       /* The object may have returned a reference to its
+                          dict, so copy it to avoid mutating it. */
+                       PyObject *temp = PyDict_Copy(masterdict);
+                       if (temp == NULL)
                                goto error;
+                       Py_DECREF(masterdict);
+                       masterdict = temp;
                }
-               for (s = attrlist; *s != NULL; s++) {
-                       m = PyObject_GetAttrString(v, *s);
-                       if (m == NULL) {
-                               PyErr_Clear();
-                               continue;
-                       }
-                       for (i = 0; ; i++) {
-                               x = PySequence_GetItem(m, i);
-                               if (x == NULL) {
-                                       PyErr_Clear();
-                                       break;
-                               }
-                               if (PyList_Append(l, x) != 0) {
-                                       Py_DECREF(x);
-                                       Py_DECREF(m);
-                                       goto error;
-                               }
-                               Py_DECREF(x);
-                       }
-                       Py_DECREF(m);
+               /* Merge in attrs reachable from its class. */
+               itsclass = PyObject_GetAttrString(arg, "__class__");
+               /* XXX Sometimes this is null!  Like after "class C: pass",
+                  C.__class__ raises AttributeError.  Don't know of other
+                  cases. */
+               if (itsclass == NULL)
+                       PyErr_Clear();
+               else {
+                       int status = merge_class_dict(masterdict, itsclass);
+                       Py_DECREF(itsclass);
+                       if (status < 0)
+                               goto error;
                }
        }
-       if (PyList_Sort(l) != 0)
+
+       assert((result == NULL) ^ (masterdict == NULL));
+       if (masterdict != NULL) {
+               /* The result comes from its keys. */
+               assert(result == NULL);
+               result = PyMapping_Keys(masterdict);
+               if (result == NULL)
+                       goto error;
+       }
+
+       assert(result);
+       if (PyList_Sort(result) != 0)
                goto error;
-       return l;
+       else
+               goto normal_return;
+
   error:
-       Py_XDECREF(l);
-       return NULL;
+       Py_XDECREF(result);
+       result = NULL;
+       /* fall through */
+  normal_return:
+       Py_XDECREF(masterdict);
+       return result;
 }
 
 static char 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.  Without an argument, the names in the current scope\n\
-are listed.  With an instance argument, only the instance attributes are\n\
-returned.  With a class argument, attributes of the base class are not\n\
-returned.  For other types or arguments, this may list members or methods.";
-
+"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 object:  its attributes, and recursively the attributes of its bases.\n"
+"Otherwise:  its attributes, its class's attributes, and recursively the\n"
+"    attributes of its class's base classes.";
 
 static PyObject *
 builtin_divmod(PyObject *self, PyObject *args)