Skip to content

Instantly share code, notes, and snippets.

@OddBloke
Created August 30, 2017 09:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save OddBloke/5492041a96a4a2a3a335a492f49cab32 to your computer and use it in GitHub Desktop.
Save OddBloke/5492041a96a4a2a3a335a492f49cab32 to your computer and use it in GitHub Desktop.
A simple typed_ast script to find nested classes
class Foo:
class Bar:
class Baz: ...
class Lolz: ...
def wibble(self):
class InMethod:
class InInMethod: ...
class Eggs: ...
def func():
class InFunc:
class InInFunc: ...
class Second:
class InSecond: ...
if True:
class InIf:
class InInIf: ...
else:
class InElse:
class InInElse: ...
class Container:
if True:
class InClassIf: ...
import sys
from typing import Any, List, NamedTuple, Union
from typed_ast.ast3 import (
AST, AsyncFunctionDef, ClassDef, FunctionDef, Module, NodeVisitor,
iter_fields, parse)
scope_creators = (AsyncFunctionDef, FunctionDef, Module)
ScopeCreator = Union[AsyncFunctionDef, FunctionDef, Module]
class ClassStack(NamedTuple):
"""A stack of nested ClassDefs built on top of a root AST."""
root: ScopeCreator
classes: List[ClassDef]
def push(self, cls: ClassDef) -> None:
self.classes.append(cls)
def pop(self) -> ClassDef:
return self.classes.pop()
def __str__(self) -> str:
first_char = ' ' if isinstance(self.root, Module) else '*'
return '{}{}'.format(first_char,
' -> '.join(cls.name for cls in self.classes))
class NestedClassFinder(NodeVisitor):
def __init__(self) -> None:
self.class_stacks: List[ClassStack] = []
self.output_lines: List[str] = []
def generic_visit(self, node: AST) -> None:
if isinstance(node, scope_creators):
self.class_stacks.append(ClassStack(node, []))
super().generic_visit(node)
if isinstance(node, scope_creators):
self.class_stacks.pop()
def visit_ClassDef(self, node: ClassDef) -> None:
current_stack = self.class_stacks[-1]
current_stack.push(node)
if len(current_stack.classes) > 1:
self.output_lines.append(str(current_stack))
self.generic_visit(node)
current_stack.pop()
def __str__(self) -> str:
return '\n'.join(self.output_lines)
if __name__ == '__main__':
filename = sys.argv[1]
ncf = NestedClassFinder()
ncf.visit(parse(open(filename).read()))
if len(ncf.output_lines):
print('--- {} ---'.format(filename))
print(ncf)
--- nested.py ---
Foo -> Bar
Foo -> Bar -> Baz
Foo -> Lolz
*InMethod -> InInMethod
Foo -> Eggs
*InFunc -> InInFunc
Second -> InSecond
InIf -> InInIf
InElse -> InInElse
Container -> InClassIf
$ for x in .../cpython/Lib/**.py; do python3 find_nested_classes.py "$x"; done
--- /home/daniel/personal_dev/cpython/Lib/argparse.py ---
HelpFormatter -> _Section
_SubParsersAction -> _ChoicesPseudoAction
--- /home/daniel/personal_dev/cpython/Lib/imaplib.py ---
IMAP4 -> error
IMAP4 -> abort
IMAP4 -> readonly
--- /home/daniel/personal_dev/cpython/Lib/profile.py ---
Profile -> fake_code
Profile -> fake_frame
--- /home/daniel/personal_dev/cpython/Lib/weakref.py ---
finalize -> _Info
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment