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.
This commit is contained in:
Tim Peters 2001-09-03 05:47:38 +00:00
parent 95c99e57b3
commit 5d2b77cf31
5 changed files with 207 additions and 65 deletions

View 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()

View 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}
>>>

View 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

View File

@ -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

View 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);
}
if (l == NULL) {
l = PyList_New(0);
if (l == NULL)
masterdict = PyDict_New();
if (masterdict == NULL)
goto error;
}
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);
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;
}
/* 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)