Commit 37a309db authored by Tim Peters's avatar Tim Peters

builtin_dir(): Treat classic classes like types. Use PyDict_Keys instead

of PyMapping_Keys because we know we have a real dict.  Tolerate that
objects may have an attr named "__dict__" that's not a dict (Py_None
popped up during testing).

test_descr.py, test_dir():  Test the new classic-class behavior; beef up
the new-style class test similarly.

test_pyclbr.py, checkModule():  dir(C) is no longer a synonym for
C.__dict__.keys() when C is a classic class (looks like the same thing
that burned distutils! -- should it be *made* a synoym again?  Then it
would be inconsistent with new-style class behavior.).
parent a8aefe53
...@@ -183,8 +183,7 @@ def test_dir(): ...@@ -183,8 +183,7 @@ def test_dir():
for arg in 2, 2L, 2j, 2e0, [2], "2", u"2", (2,), {2:2}, type, test_dir: for arg in 2, 2L, 2j, 2e0, [2], "2", u"2", (2,), {2:2}, type, test_dir:
dir(arg) dir(arg)
# Check some details here because classic classes aren't working # Try classic classes.
# reasonably, and I want this to fail (eventually).
class C: class C:
Cdata = 1 Cdata = 1
def Cmethod(self): pass def Cmethod(self): pass
...@@ -202,23 +201,45 @@ def test_dir(): ...@@ -202,23 +201,45 @@ def test_dir():
class A(C): class A(C):
Adata = 1 Adata = 1
def Amethod(self): pass def Amethod(self): pass
astuff = ['Adata', 'Amethod', '__doc__', '__module__']
# This isn't finding C's stuff at all. astuff = ['Adata', 'Amethod'] + cstuff
verify(dir(A) == astuff) verify(dir(A) == astuff)
# But this is! It's because a.__class__ exists but A.__class__ doesn't.
a = A() a = A()
verify(dir(a) == astuff[:2] + cstuff) verify(dir(a) == astuff)
a.adata = 42
a.amethod = lambda self: 3
verify(dir(a) == astuff + ['adata', 'amethod'])
# The same, but with new-style classes. Since these have object as a
# base class, a lot more gets sucked in.
def interesting(strings):
return [s for s in strings if not s.startswith('_')]
# The story for new-style classes is quite different.
class C(object): class C(object):
Cdata = 1 Cdata = 1
def Cmethod(self): pass def Cmethod(self): pass
cstuff = ['Cdata', 'Cmethod']
verify(interesting(dir(C)) == cstuff)
c = C()
verify(interesting(dir(c)) == cstuff)
c.cdata = 2
c.cmethod = lambda self: 0
verify(interesting(dir(c)) == cstuff + ['cdata', 'cmethod'])
class A(C): class A(C):
Adata = 1 Adata = 1
def Amethod(self): pass def Amethod(self): pass
d = dir(A)
for expected in 'Cdata', 'Cmethod', 'Adata', 'Amethod': astuff = ['Adata', 'Amethod'] + cstuff
verify(expected in d) verify(interesting(dir(A)) == astuff)
a = A()
verify(interesting(dir(a)) == astuff)
a.adata = 42
a.amethod = lambda self: 3
verify(interesting(dir(a)) == astuff + ['adata', 'amethod'])
binops = { binops = {
'add': '+', 'add': '+',
......
...@@ -76,7 +76,7 @@ class PyclbrTest(unittest.TestCase): ...@@ -76,7 +76,7 @@ class PyclbrTest(unittest.TestCase):
self.assertListEq(real_bases, pyclbr_bases, ignore) self.assertListEq(real_bases, pyclbr_bases, ignore)
actualMethods = [] actualMethods = []
for m in dir(py_item): for m in py_item.__dict__.keys():
if type(getattr(py_item, m)) == MethodType: if type(getattr(py_item, m)) == MethodType:
actualMethods.append(m) actualMethods.append(m)
foundMethods = [] foundMethods = []
......
...@@ -440,9 +440,6 @@ merge_class_dict(PyObject* dict, PyObject* aclass) ...@@ -440,9 +440,6 @@ merge_class_dict(PyObject* dict, PyObject* aclass)
PyObject *bases; PyObject *bases;
assert(PyDict_Check(dict)); assert(PyDict_Check(dict));
/* XXX Class objects fail the PyType_Check check. Don't
XXX know of others. */
/* assert(PyType_Check(aclass)); */
assert(aclass); assert(aclass);
/* Merge in the type's dict (if any). */ /* Merge in the type's dict (if any). */
...@@ -490,7 +487,7 @@ builtin_dir(PyObject *self, PyObject *args) ...@@ -490,7 +487,7 @@ builtin_dir(PyObject *self, PyObject *args)
PyObject *locals = PyEval_GetLocals(); PyObject *locals = PyEval_GetLocals();
if (locals == NULL) if (locals == NULL)
goto error; goto error;
result = PyMapping_Keys(locals); result = PyDict_Keys(locals);
if (result == NULL) if (result == NULL)
goto error; goto error;
} }
...@@ -500,10 +497,13 @@ builtin_dir(PyObject *self, PyObject *args) ...@@ -500,10 +497,13 @@ builtin_dir(PyObject *self, PyObject *args)
masterdict = PyObject_GetAttrString(arg, "__dict__"); masterdict = PyObject_GetAttrString(arg, "__dict__");
if (masterdict == NULL) if (masterdict == NULL)
goto error; goto error;
assert(PyDict_Check(masterdict));
} }
/* Elif some form of type, recurse. */ /* Elif some form of type or class, grab its dict and its bases.
else if (PyType_Check(arg)) { 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) || PyClass_Check(arg)) {
masterdict = PyDict_New(); masterdict = PyDict_New();
if (masterdict == NULL) if (masterdict == NULL)
goto error; goto error;
...@@ -514,28 +514,30 @@ builtin_dir(PyObject *self, PyObject *args) ...@@ -514,28 +514,30 @@ builtin_dir(PyObject *self, PyObject *args)
/* Else look at its dict, and the attrs reachable from its class. */ /* Else look at its dict, and the attrs reachable from its class. */
else { else {
PyObject *itsclass; PyObject *itsclass;
/* Create a dict to start with. */ /* Create a dict to start with. CAUTION: Not everything
responding to __dict__ returns a dict! */
masterdict = PyObject_GetAttrString(arg, "__dict__"); masterdict = PyObject_GetAttrString(arg, "__dict__");
if (masterdict == NULL) { if (masterdict == NULL) {
PyErr_Clear(); PyErr_Clear();
masterdict = PyDict_New(); masterdict = PyDict_New();
if (masterdict == NULL) }
goto error; else if (!PyDict_Check(masterdict)) {
Py_DECREF(masterdict);
masterdict = PyDict_New();
} }
else { else {
/* The object may have returned a reference to its /* The object may have returned a reference to its
dict, so copy it to avoid mutating it. */ dict, so copy it to avoid mutating it. */
PyObject *temp = PyDict_Copy(masterdict); PyObject *temp = PyDict_Copy(masterdict);
if (temp == NULL)
goto error;
Py_DECREF(masterdict); Py_DECREF(masterdict);
masterdict = temp; masterdict = temp;
} }
/* Merge in attrs reachable from its class. */ if (masterdict == NULL)
goto error;
/* Merge in attrs reachable from its class.
CAUTION: Not all objects have a __class__ attr. */
itsclass = PyObject_GetAttrString(arg, "__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) if (itsclass == NULL)
PyErr_Clear(); PyErr_Clear();
else { else {
...@@ -550,7 +552,7 @@ builtin_dir(PyObject *self, PyObject *args) ...@@ -550,7 +552,7 @@ builtin_dir(PyObject *self, PyObject *args)
if (masterdict != NULL) { if (masterdict != NULL) {
/* The result comes from its keys. */ /* The result comes from its keys. */
assert(result == NULL); assert(result == NULL);
result = PyMapping_Keys(masterdict); result = PyDict_Keys(masterdict);
if (result == NULL) if (result == NULL)
goto error; goto error;
} }
...@@ -578,7 +580,8 @@ static char dir_doc[] = ...@@ -578,7 +580,8 @@ static char dir_doc[] =
"\n" "\n"
"No argument: the names in the current scope.\n" "No argument: the names in the current scope.\n"
"Module object: the module attributes.\n" "Module object: the module attributes.\n"
"Type object: its attributes, and recursively the attributes of its bases.\n" "Type or class object: its attributes, and recursively the attributes of\n"
" its bases.\n"
"Otherwise: its attributes, its class's attributes, and recursively the\n" "Otherwise: its attributes, its class's attributes, and recursively the\n"
" attributes of its class's base classes."; " attributes of its class's base classes.";
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment