Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 37a309d

Browse files
committed
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.).
1 parent a8aefe5 commit 37a309d

3 files changed

Lines changed: 52 additions & 28 deletions

File tree

Lib/test/test_descr.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,7 @@ def test_dir():
183183
for arg in 2, 2L, 2j, 2e0, [2], "2", u"2", (2,), {2:2}, type, test_dir:
184184
dir(arg)
185185

186-
# Check some details here because classic classes aren't working
187-
# reasonably, and I want this to fail (eventually).
186+
# Try classic classes.
188187
class C:
189188
Cdata = 1
190189
def Cmethod(self): pass
@@ -202,23 +201,45 @@ def Cmethod(self): pass
202201
class A(C):
203202
Adata = 1
204203
def Amethod(self): pass
205-
astuff = ['Adata', 'Amethod', '__doc__', '__module__']
206-
# This isn't finding C's stuff at all.
204+
205+
astuff = ['Adata', 'Amethod'] + cstuff
207206
verify(dir(A) == astuff)
208-
# But this is! It's because a.__class__ exists but A.__class__ doesn't.
209207
a = A()
210-
verify(dir(a) == astuff[:2] + cstuff)
208+
verify(dir(a) == astuff)
209+
a.adata = 42
210+
a.amethod = lambda self: 3
211+
verify(dir(a) == astuff + ['adata', 'amethod'])
212+
213+
# The same, but with new-style classes. Since these have object as a
214+
# base class, a lot more gets sucked in.
215+
def interesting(strings):
216+
return [s for s in strings if not s.startswith('_')]
211217

212-
# The story for new-style classes is quite different.
213218
class C(object):
214219
Cdata = 1
215220
def Cmethod(self): pass
221+
222+
cstuff = ['Cdata', 'Cmethod']
223+
verify(interesting(dir(C)) == cstuff)
224+
225+
c = C()
226+
verify(interesting(dir(c)) == cstuff)
227+
228+
c.cdata = 2
229+
c.cmethod = lambda self: 0
230+
verify(interesting(dir(c)) == cstuff + ['cdata', 'cmethod'])
231+
216232
class A(C):
217233
Adata = 1
218234
def Amethod(self): pass
219-
d = dir(A)
220-
for expected in 'Cdata', 'Cmethod', 'Adata', 'Amethod':
221-
verify(expected in d)
235+
236+
astuff = ['Adata', 'Amethod'] + cstuff
237+
verify(interesting(dir(A)) == astuff)
238+
a = A()
239+
verify(interesting(dir(a)) == astuff)
240+
a.adata = 42
241+
a.amethod = lambda self: 3
242+
verify(interesting(dir(a)) == astuff + ['adata', 'amethod'])
222243

223244
binops = {
224245
'add': '+',

Lib/test/test_pyclbr.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def checkModule(self, moduleName, module=None, ignore=()):
7676
self.assertListEq(real_bases, pyclbr_bases, ignore)
7777

7878
actualMethods = []
79-
for m in dir(py_item):
79+
for m in py_item.__dict__.keys():
8080
if type(getattr(py_item, m)) == MethodType:
8181
actualMethods.append(m)
8282
foundMethods = []

Python/bltinmodule.c

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -440,9 +440,6 @@ merge_class_dict(PyObject* dict, PyObject* aclass)
440440
PyObject *bases;
441441

442442
assert(PyDict_Check(dict));
443-
/* XXX Class objects fail the PyType_Check check. Don't
444-
XXX know of others. */
445-
/* assert(PyType_Check(aclass)); */
446443
assert(aclass);
447444

448445
/* Merge in the type's dict (if any). */
@@ -490,7 +487,7 @@ builtin_dir(PyObject *self, PyObject *args)
490487
PyObject *locals = PyEval_GetLocals();
491488
if (locals == NULL)
492489
goto error;
493-
result = PyMapping_Keys(locals);
490+
result = PyDict_Keys(locals);
494491
if (result == NULL)
495492
goto error;
496493
}
@@ -500,10 +497,13 @@ builtin_dir(PyObject *self, PyObject *args)
500497
masterdict = PyObject_GetAttrString(arg, "__dict__");
501498
if (masterdict == NULL)
502499
goto error;
500+
assert(PyDict_Check(masterdict));
503501
}
504502

505-
/* Elif some form of type, recurse. */
506-
else if (PyType_Check(arg)) {
503+
/* Elif some form of type or class, grab its dict and its bases.
504+
We deliberately don't suck up its __class__, as methods belonging
505+
to the metaclass would probably be more confusing than helpful. */
506+
else if (PyType_Check(arg) || PyClass_Check(arg)) {
507507
masterdict = PyDict_New();
508508
if (masterdict == NULL)
509509
goto error;
@@ -514,28 +514,30 @@ builtin_dir(PyObject *self, PyObject *args)
514514
/* Else look at its dict, and the attrs reachable from its class. */
515515
else {
516516
PyObject *itsclass;
517-
/* Create a dict to start with. */
517+
/* Create a dict to start with. CAUTION: Not everything
518+
responding to __dict__ returns a dict! */
518519
masterdict = PyObject_GetAttrString(arg, "__dict__");
519520
if (masterdict == NULL) {
520521
PyErr_Clear();
521522
masterdict = PyDict_New();
522-
if (masterdict == NULL)
523-
goto error;
523+
}
524+
else if (!PyDict_Check(masterdict)) {
525+
Py_DECREF(masterdict);
526+
masterdict = PyDict_New();
524527
}
525528
else {
526529
/* The object may have returned a reference to its
527530
dict, so copy it to avoid mutating it. */
528531
PyObject *temp = PyDict_Copy(masterdict);
529-
if (temp == NULL)
530-
goto error;
531532
Py_DECREF(masterdict);
532533
masterdict = temp;
533534
}
534-
/* Merge in attrs reachable from its class. */
535+
if (masterdict == NULL)
536+
goto error;
537+
538+
/* Merge in attrs reachable from its class.
539+
CAUTION: Not all objects have a __class__ attr. */
535540
itsclass = PyObject_GetAttrString(arg, "__class__");
536-
/* XXX Sometimes this is null! Like after "class C: pass",
537-
C.__class__ raises AttributeError. Don't know of other
538-
cases. */
539541
if (itsclass == NULL)
540542
PyErr_Clear();
541543
else {
@@ -550,7 +552,7 @@ builtin_dir(PyObject *self, PyObject *args)
550552
if (masterdict != NULL) {
551553
/* The result comes from its keys. */
552554
assert(result == NULL);
553-
result = PyMapping_Keys(masterdict);
555+
result = PyDict_Keys(masterdict);
554556
if (result == NULL)
555557
goto error;
556558
}
@@ -578,7 +580,8 @@ static char dir_doc[] =
578580
"\n"
579581
"No argument: the names in the current scope.\n"
580582
"Module object: the module attributes.\n"
581-
"Type object: its attributes, and recursively the attributes of its bases.\n"
583+
"Type or class object: its attributes, and recursively the attributes of\n"
584+
" its bases.\n"
582585
"Otherwise: its attributes, its class's attributes, and recursively the\n"
583586
" attributes of its class's base classes.";
584587

0 commit comments

Comments
 (0)