""" Exercise 1 – Find All Classes and Top-Level Functions ===================================================== AISE501 · AST Exercises · Spring Semester 2026 Learning goals -------------- * Parse a Python source file into an AST using ``ast.parse()``. * Walk the tree with ``ast.walk()`` to discover ``ClassDef`` and ``FunctionDef`` nodes. * Extract basic metadata: name, line number, docstring. Tasks ----- Part A Parse sample_stats.py and list every top-level class (TODO 1-2). Part B List every top-level (module-level) function (TODO 3-4). Part C Extract the docstring of each class and function (TODO 5-6). """ import ast from pathlib import Path SOURCE_FILE = Path(__file__).parent / "sample_stats.py" source_code = SOURCE_FILE.read_text() # Parse the source code into an AST tree = ast.parse(source_code) # ── Part A: Find All Classes ──────────────────────────────────────────────── # Iterate over the top-level body of the module and collect ClassDef nodes. print("=" * 60) print("Part A – Classes in sample_stats.py") print("=" * 60) # TODO 1: Create a list called `classes` that contains every ast.ClassDef # node found in tree.body (the top-level statements). # # Hint: Use a list comprehension: # [node for node in tree.body if isinstance(node, ???)] classes = [node for node in tree.body if isinstance(node, ast.ClassDef)] # TODO: replace with list comprehension # TODO 2: Print each class name and its line number. # Access node.name and node.lineno. max_class_length = str(max([len(cls.name) for cls in classes])) for cls in classes: # TODO: print class name and line number print(f"{cls.name:{max_class_length}} | Line: {cls.lineno}") # ── Part B: Find All Top-Level Functions ──────────────────────────────────── # Same approach, but filter for FunctionDef instead of ClassDef. print("\n" + "=" * 60) print("Part B – Top-level functions in sample_stats.py") print("=" * 60) # TODO 3: Create a list called `functions` that contains every # ast.FunctionDef node found in tree.body. functions = [node for node in tree.body if isinstance(node, ast.FunctionDef)] # TODO: replace with list comprehension # TODO 4: Print each function name and its line number. max_function_length = str(max([len(func.name) for func in functions])) for func in functions: # TODO: print function name and line number print(f"{func.name:{max_function_length}} | Line: {func.lineno}") # ── Part C: Extract Docstrings ────────────────────────────────────────────── # In Python's AST a docstring is the first statement in the body of a # class or function, if that statement is an ast.Expr whose value is an # ast.Constant with a string value. print("\n" + "=" * 60) print("Part C – Docstrings") print("=" * 60) # TODO 5: Write a helper function get_docstring(node) that returns the # docstring of a ClassDef or FunctionDef, or None if there is none. # # Hint: You can also use ast.get_docstring(node) from the standard library, # but try implementing it manually first to understand the tree structure. # # Manual approach: # 1. Check if node.body is non-empty. # 2. Check if the first element is an ast.Expr. # 3. Check if that Expr's .value is an ast.Constant with a str value. # 4. If all checks pass, return the string; otherwise return None. def get_docstring(node): """Return the docstring of *node*, or None.""" if not node.body: return None if isinstance(node.body[0], ast.Expr): if isinstance(node.body[0].value, ast.Constant): return str(node.body[0].value.s) return None # TODO 6: For each class and function, print its name and docstring (first line only). print("\nClasses:") for cls in classes: doc = get_docstring(cls) # TODO: print cls.name and the first line of doc (or "No docstring") if not doc: doc = "No docstring" print(f"{cls.name:{max_class_length}} | {doc.split('\n')[0]}") print("\nFunctions:") for func in functions: doc = get_docstring(func) # TODO: print func.name and the first line of doc (or "No docstring") if not doc: doc = "No docstring" print(f"{func.name:{max_function_length}} | {doc.split('\n')[0]}") # ── Expected Output (abbreviated) ────────────────────────────────────────── # Part A should list: DataCleaner, DescriptiveStats, HypothesisTester, # CurveFitter, ReportGenerator # Part B should list: load_csv, save_json, validate_data, run_analysis_pipeline # Part C should show the first line of each docstring.