Patch #1591665: implement the __dir__() special function lookup in PyObject_Dir.

This commit is contained in:
Georg Brandl 2007-03-10 22:13:27 +00:00
parent af334387d1
commit e32b4224d0
5 changed files with 266 additions and 169 deletions

View file

@ -1284,6 +1284,8 @@ PyCallable_Check(PyObject *x)
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 @@ merge_class_dict(PyObject* dict, PyObject* aclass)
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.
*/
static int
merge_list_attr(PyObject* dict, PyObject* obj, const char *attrname)
/* Helper for PyObject_Dir without arguments: returns the local scope. */
static PyObject *
_dir_locals()
{
PyObject *list;
int result = 0;
PyObject *locals = PyEval_GetLocals();
assert(PyDict_Check(dict));
assert(obj);
assert(attrname);
list = PyObject_GetAttrString(obj, attrname);
if (list == NULL)
PyErr_Clear();
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;
}
}
if (locals == NULL) {
PyErr_SetString(PyExc_SystemError, "frame does not exist");
return NULL;
}
Py_XDECREF(list);
/* 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 *result = NULL;
PyObject *dict = PyDict_New();
if (dict != NULL && merge_class_dict(dict, obj) == 0)
result = PyDict_Keys(dict);
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 module objects: returns the module's __dict__. */
static PyObject *
_specialized_dir_module(PyObject *obj)
{
/* Set exactly one of these non-NULL before the end. */
PyObject *result = NULL; /* result list */
PyObject *masterdict = NULL; /* result is masterdict.keys() */
PyObject *result = NULL;
PyObject *dict = PyObject_GetAttrString(obj, "__dict__");
/* 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;
}
/* 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;
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));
}
}
/* 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;
}
Py_XDECREF(dict);
return result;
}
/* Else look at its dict, and the attrs reachable from its class. */
/* Helper for PyObject_Dir of generic objects: returns __dict__, __class__,
and recursively up the __class__.__bases__ chain.
*/
static PyObject *
_generic_dir(PyObject *obj)
{
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();
}
else if (!PyDict_Check(dict)) {
Py_DECREF(dict);
dict = PyDict_New();
}
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)
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;
/* 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;
}
/* Copy __dict__ to avoid mutating it. */
PyObject *temp = PyDict_Copy(dict);
Py_DECREF(dict);
dict = temp;
}
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(result);
if (!PyList_Check(result)) {
PyErr_Format(PyExc_TypeError,
"Expected keys() to be a list, not '%.200s'",
result->ob_type->tp_name);
if (dict == NULL)
goto error;
}
if (PyList_Sort(result) != 0)
goto error;
else
goto normal_return;
error:
Py_XDECREF(result);
result = NULL;
/* 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 {
if (merge_class_dict(dict, itsclass) != 0)
goto error;
}
result = PyDict_Keys(dict);
/* fall through */
normal_return:
Py_XDECREF(masterdict);
error:
Py_XDECREF(itsclass);
Py_XDECREF(dict);
return result;
}
/* 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(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;
/* 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;
}
}
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
/* object -- introspect the object */
result = _dir_object(obj);
assert(result == NULL || PyList_Check(result));
if (result != NULL && PyList_Sort(result) != 0) {
/* sorting the list failed */
Py_DECREF(result);
result = NULL;
}
return result;
}