diff options
author | Tim Peters <tim.peters@gmail.com> | 2001-09-03 05:47:38 (GMT) |
---|---|---|
committer | Tim Peters <tim.peters@gmail.com> | 2001-09-03 05:47:38 (GMT) |
commit | 5d2b77cf31c5a3cbabc74936831480b9caea3a12 (patch) | |
tree | dabb2f29553f94a18e3c5ae96d6f232196415f50 | |
parent | 95c99e57b37ede725af1fdd1ff914c91284e3048 (diff) | |
download | cpython-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.py | 49 | ||||
-rw-r--r-- | Lib/test/test_descrtut.py | 9 | ||||
-rw-r--r-- | Lib/test/test_generators.py | 8 | ||||
-rw-r--r-- | Misc/NEWS | 17 | ||||
-rw-r--r-- | Python/bltinmodule.c | 187 |
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 @@ -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) |