diff -r 7af470b0fa5e Lib/inspect.py --- a/Lib/inspect.py Fri Apr 08 13:39:59 2011 +0200 +++ b/Lib/inspect.py Sat Apr 09 03:19:47 2011 +0200 @@ -1102,7 +1102,8 @@ except KeyError: pass else: - if not (type(class_dict) is types.GetSetDescriptorType and + if not (type(class_dict) is types.MemberDescriptorType or + type(class_dict) is types.GetSetDescriptorType and class_dict.__name__ == "__dict__" and class_dict.__objclass__ is entry): return True @@ -1151,6 +1152,66 @@ raise AttributeError(attr) +# ---------------------------------------------------- static version of dir +def _static_getbases(cls): + return type.__dict__["__bases__"].__get__(cls) + +def _merge_class_dict(result, cls): + if not _shadowed_dict(type(cls)): + result.update(cls.__dict__) + + # Recursively merge in the base types' dicts + for base in _static_getbases(cls): + _merge_class_dict(result, base) + +def _is_module(cls): + return (cls is types.ModuleType or + any(base is types.ModuleType for base in _static_getbases(cls))) + +def _dir_locals(): + try: + frame = sys._getframe(2) + except AttributeError: + return {} + else: + return sorted(frame.f_locals) + +def _dir_module(mod): + if not _shadowed_dict(type(mod)): + return mod.__dict__ + else: + return {} + +def dir_static(obj=_sentinel): + """Like the built-in `dir()` but without triggering any dynamic lookup.""" + if obj is _sentinel: + # Special case -- dir without an argument returns the locals + return _dir_locals() + + if not _is_type(obj): + cls = type(obj) + else: + cls = obj + + result = {} + if _is_module(cls): + result = _dir_module(obj) + elif cls is obj: + _merge_class_dict(result, cls) + else: + # Get (a copy of) __dict__ + if not _shadowed_dict(cls): + try: + result = obj.__dict__.copy() + except AttributeError: + pass + + # Merge in attributes reachable from its class + _merge_class_dict(result, cls) + + return sorted(result) + + GEN_CREATED = 'GEN_CREATED' GEN_RUNNING = 'GEN_RUNNING' GEN_SUSPENDED = 'GEN_SUSPENDED' diff -r 7af470b0fa5e Lib/test/test_inspect.py --- a/Lib/test/test_inspect.py Fri Apr 08 13:39:59 2011 +0200 +++ b/Lib/test/test_inspect.py Sat Apr 09 03:19:47 2011 +0200 @@ -987,6 +987,62 @@ self.assertEqual(inspect.getattr_static(instance, "spam"), 42) self.assertFalse(Thing.executed) +class TestDirStatic(unittest.TestCase): + def test_locals(self): + local_name = 42 + self.assertIn("local_name", inspect.dir_static()) + + def test_module(self): + self.assertIn("exit", inspect.dir_static(sys)) + + def test_module_shadowed_dict(self): + class Module(types.ModuleType): + executed = False + + @property + def __dict__(self): + self.executed = True + return {"invalid": 42} + + module = Module("test") + self.assertNotIn("invalid", inspect.dir_static(module)) + self.assertFalse(module.executed) + + def test_no_dict(self): + class Thing: + __slots__ = [] + + thing = Thing() + self.assertIn("__repr__", inspect.dir_static(thing)) + + def test_obj(self): + class Thing: + x = 1 + + def __init__(self): + self.y = 2 + + thing = Thing() + result = inspect.dir_static(thing) + self.assertIn("x", result) + self.assertIn("y", result) + + def test_type(self): + self.assertIn("strip", inspect.dir_static(str)) + self.assertNotIn("__mro__", inspect.dir_static(str)) + + def test_dir_method(self): + class Thing: + executed = False + + def __dir__(self): + self.executed = True + return ["invalid_attribute"] + + thing = Thing() + self.assertNotIn("invalid_attribute", inspect.dir_static(thing)) + self.assertFalse(thing.executed) + class TestGetGeneratorState(unittest.TestCase): def setUp(self): @@ -1046,7 +1102,7 @@ TestInterpreterStack, TestClassesAndFunctions, TestPredicates, TestGetcallargsFunctions, TestGetcallargsMethods, TestGetcallargsUnboundMethods, TestGetattrStatic, TestGetGeneratorState, - TestNoEOL + TestNoEOL, TestDirStatic ) if __name__ == "__main__":