AISE501_CLASS/AST Files/ex01_find_classes_functions.py
2026-05-03 20:27:09 +02:00

134 lines
4.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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.