summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Peters <tim.peters@gmail.com>2001-09-03 05:47:38 (GMT)
committerTim Peters <tim.peters@gmail.com>2001-09-03 05:47:38 (GMT)
commit5d2b77cf31c5a3cbabc74936831480b9caea3a12 (patch)
treedabb2f29553f94a18e3c5ae96d6f232196415f50
parent95c99e57b37ede725af1fdd1ff914c91284e3048 (diff)
downloadcpython-5d2b77cf31c5a3cbabc74936831480b9caea3a12.zip
cpython-5d2b77cf31c5a3cbabc74936831480b9caea3a12.tar.gz
cpython-5d2b77cf31c5a3cbabc74936831480b9caea3a12.tar.bz2
Make dir() wordier (see the new docstring). The new behavior is a mixed
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.
-rw-r--r--Lib/test/test_descr.py49
-rw-r--r--Lib/test/test_descrtut.py9
-rw-r--r--Lib/test/test_generators.py8
-rw-r--r--Misc/NEWS17
-rw-r--r--Python/bltinmodule.c187
5 files changed, 206 insertions, 64 deletions
diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py
index f6b9e1b..ee924dc 100644
--- a/Lib/test/test_descr.py
+++ b/Lib/test/test_descr.py
@@ -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()
diff --git a/Lib/test/test_descrtut.py b/Lib/test/test_descrtut.py
index edb0388..121eed5 100644
--- a/Lib/test/test_descrtut.py
+++ b/Lib/test/test_descrtut.py
@@ -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}
>>>
diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py
index 947e26f..0e9d060 100644
--- a/Lib/test/test_generators.py
+++ b/Lib/test/test_generators.py
@@ -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
diff --git a/Misc/NEWS b/Misc/NEWS
index 55e2993..a1557f8 100644
--- 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
diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c
index 62bf2a7..d5dc322 100644
--- a/Python/bltinmodule.c
+++ b/Python/bltinmodule.c
@@ -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)