{ "cells": [ { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "# Class Diagrams\n", "\n", "This is a simple viewer for class diagrams. Customized towards the book." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "**Prerequisites**\n", "\n", "* _Refer to earlier chapters as notebooks here, as here:_ [Earlier Chapter](Debugger.ipynb)." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "button": false, "execution": { "iopub.execute_input": "2025-01-16T09:55:42.407357Z", "iopub.status.busy": "2025-01-16T09:55:42.407264Z", "iopub.status.idle": "2025-01-16T09:55:42.476673Z", "shell.execute_reply": "2025-01-16T09:55:42.476324Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import bookutils.setup" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Synopsis\n", "\n", "\n", "To [use the code provided in this chapter](Importing.ipynb), write\n", "\n", "```python\n", ">>> from debuggingbook.ClassDiagram import \n", "```\n", "\n", "and then make use of the following features.\n", "\n", "\n", "The function `display_class_hierarchy()` function shows the class hierarchy for the given class (or list of classes). \n", "* The keyword parameter `public_methods`, if given, is a list of \"public\" methods to be used by clients (default: all methods with docstrings).\n", "* The keyword parameter `abstract_classes`, if given, is a list of classes to be displayed as \"abstract\" (i.e. with a cursive class name).\n", "\n", "```python\n", ">>> display_class_hierarchy(D_Class, abstract_classes=[A_Class])\n", "```\n", "![](PICS/ClassDiagram-synopsis-1.svg)\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": true, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "## Getting a Class Hierarchy" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.478722Z", "iopub.status.busy": "2025-01-16T09:55:42.478598Z", "iopub.status.idle": "2025-01-16T09:55:42.480575Z", "shell.execute_reply": "2025-01-16T09:55:42.480284Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import inspect" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Using `mro()`, we can access the class hierarchy. We make sure to avoid duplicates created by `class X(X)`." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.482495Z", "iopub.status.busy": "2025-01-16T09:55:42.482356Z", "iopub.status.idle": "2025-01-16T09:55:42.484410Z", "shell.execute_reply": "2025-01-16T09:55:42.484095Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "# ignore\n", "from typing import Callable, Dict, Type, Set, List, Union, Any, Tuple, Optional" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.485716Z", "iopub.status.busy": "2025-01-16T09:55:42.485611Z", "iopub.status.idle": "2025-01-16T09:55:42.487847Z", "shell.execute_reply": "2025-01-16T09:55:42.487574Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def class_hierarchy(cls: Type) -> List[Type]:\n", " superclasses = cls.mro()\n", " hierarchy = []\n", " last_superclass_name = \"\"\n", "\n", " for superclass in superclasses:\n", " if superclass.__name__ != last_superclass_name:\n", " hierarchy.append(superclass)\n", " last_superclass_name = superclass.__name__\n", "\n", " return hierarchy" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Here's an example:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.489161Z", "iopub.status.busy": "2025-01-16T09:55:42.489056Z", "iopub.status.idle": "2025-01-16T09:55:42.490871Z", "shell.execute_reply": "2025-01-16T09:55:42.490636Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class A_Class:\n", " \"\"\"A Class which does A thing right.\n", " Comes with a longer docstring.\"\"\"\n", "\n", " def foo(self) -> None:\n", " \"\"\"The Adventures of the glorious Foo\"\"\"\n", " pass\n", "\n", " def quux(self) -> None:\n", " \"\"\"A method that is not used.\"\"\"\n", " pass" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.492267Z", "iopub.status.busy": "2025-01-16T09:55:42.492155Z", "iopub.status.idle": "2025-01-16T09:55:42.494068Z", "shell.execute_reply": "2025-01-16T09:55:42.493771Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class A_Class(A_Class):\n", " # We define another function in a separate cell.\n", "\n", " def second(self) -> None:\n", " pass" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.495614Z", "iopub.status.busy": "2025-01-16T09:55:42.495487Z", "iopub.status.idle": "2025-01-16T09:55:42.497673Z", "shell.execute_reply": "2025-01-16T09:55:42.497406Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class B_Class(A_Class):\n", " \"\"\"A subclass inheriting some methods.\"\"\"\n", "\n", " VAR = \"A variable\"\n", "\n", " def foo(self) -> None:\n", " \"\"\"A WW2 foo fighter.\"\"\"\n", " pass\n", "\n", " def bar(self, qux: Any = None, bartender: int = 42) -> None:\n", " \"\"\"A qux walks into a bar.\n", " `bartender` is an optional attribute.\"\"\"\n", " pass" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.499095Z", "iopub.status.busy": "2025-01-16T09:55:42.498977Z", "iopub.status.idle": "2025-01-16T09:55:42.500951Z", "shell.execute_reply": "2025-01-16T09:55:42.500676Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "SomeType = List[Optional[Union[str, int]]]" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.502394Z", "iopub.status.busy": "2025-01-16T09:55:42.502266Z", "iopub.status.idle": "2025-01-16T09:55:42.504524Z", "shell.execute_reply": "2025-01-16T09:55:42.504215Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class C_Class:\n", " \"\"\"A class injecting some method\"\"\"\n", "\n", " def qux(self, arg: SomeType) -> SomeType:\n", " return arg" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.506391Z", "iopub.status.busy": "2025-01-16T09:55:42.506044Z", "iopub.status.idle": "2025-01-16T09:55:42.508420Z", "shell.execute_reply": "2025-01-16T09:55:42.508067Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class D_Class(B_Class, C_Class):\n", " \"\"\"A subclass inheriting from multiple superclasses.\n", " Comes with a fairly long, but meaningless documentation.\"\"\"\n", "\n", " def foo(self) -> None:\n", " B_Class.foo(self)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.510089Z", "iopub.status.busy": "2025-01-16T09:55:42.509943Z", "iopub.status.idle": "2025-01-16T09:55:42.511980Z", "shell.execute_reply": "2025-01-16T09:55:42.511635Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class D_Class(D_Class):\n", " pass # An incremental addiiton that should not impact D's semantics" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.513617Z", "iopub.status.busy": "2025-01-16T09:55:42.513486Z", "iopub.status.idle": "2025-01-16T09:55:42.517608Z", "shell.execute_reply": "2025-01-16T09:55:42.516893Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "[__main__.D_Class,\n", " __main__.B_Class,\n", " __main__.A_Class,\n", " __main__.C_Class,\n", " object]" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class_hierarchy(D_Class)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Getting a Class Tree" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "We can use `__bases__` to obtain the immediate base classes." ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.541143Z", "iopub.status.busy": "2025-01-16T09:55:42.540991Z", "iopub.status.idle": "2025-01-16T09:55:42.543488Z", "shell.execute_reply": "2025-01-16T09:55:42.543151Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "(__main__.D_Class,)" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "D_Class.__bases__" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "`class_tree()` returns a class tree, using the \"lowest\" (most specialized) class with the same name." ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.545010Z", "iopub.status.busy": "2025-01-16T09:55:42.544876Z", "iopub.status.idle": "2025-01-16T09:55:42.547187Z", "shell.execute_reply": "2025-01-16T09:55:42.546822Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def class_tree(cls: Type, lowest: Optional[Type] = None) -> List[Tuple[Type, List]]:\n", " ret = []\n", " for base in cls.__bases__:\n", " if base.__name__ == cls.__name__:\n", " if not lowest:\n", " lowest = cls\n", " ret += class_tree(base, lowest)\n", " else:\n", " if lowest:\n", " cls = lowest\n", " ret.append((cls, class_tree(base)))\n", "\n", " return ret" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.548498Z", "iopub.status.busy": "2025-01-16T09:55:42.548373Z", "iopub.status.idle": "2025-01-16T09:55:42.551011Z", "shell.execute_reply": "2025-01-16T09:55:42.550528Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "[(__main__.D_Class, [(__main__.B_Class, [(__main__.A_Class, [])])]),\n", " (__main__.D_Class, [(__main__.C_Class, [])])]" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class_tree(D_Class)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.552557Z", "iopub.status.busy": "2025-01-16T09:55:42.552439Z", "iopub.status.idle": "2025-01-16T09:55:42.554856Z", "shell.execute_reply": "2025-01-16T09:55:42.554559Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "__main__.D_Class" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class_tree(D_Class)[0][0]" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.556225Z", "iopub.status.busy": "2025-01-16T09:55:42.556095Z", "iopub.status.idle": "2025-01-16T09:55:42.557833Z", "shell.execute_reply": "2025-01-16T09:55:42.557590Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "assert class_tree(D_Class)[0][0] == D_Class" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "`class_set()` flattens the tree into a set:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.559015Z", "iopub.status.busy": "2025-01-16T09:55:42.558933Z", "iopub.status.idle": "2025-01-16T09:55:42.561935Z", "shell.execute_reply": "2025-01-16T09:55:42.561642Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def class_set(classes: Union[Type, List[Type]]) -> Set[Type]:\n", " if not isinstance(classes, list):\n", " classes = [classes]\n", "\n", " ret = set()\n", "\n", " def traverse_tree(tree: List[Tuple[Type, List]]) -> None:\n", " for (cls, subtrees) in tree:\n", " ret.add(cls)\n", " for subtree in subtrees:\n", " traverse_tree(subtrees)\n", "\n", " for cls in classes:\n", " traverse_tree(class_tree(cls))\n", "\n", " return ret" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.564254Z", "iopub.status.busy": "2025-01-16T09:55:42.563990Z", "iopub.status.idle": "2025-01-16T09:55:42.566940Z", "shell.execute_reply": "2025-01-16T09:55:42.566518Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "{__main__.A_Class, __main__.B_Class, __main__.C_Class, __main__.D_Class}" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class_set(D_Class)" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.568702Z", "iopub.status.busy": "2025-01-16T09:55:42.568578Z", "iopub.status.idle": "2025-01-16T09:55:42.570565Z", "shell.execute_reply": "2025-01-16T09:55:42.570212Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "assert A_Class in class_set(D_Class)" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.572292Z", "iopub.status.busy": "2025-01-16T09:55:42.572065Z", "iopub.status.idle": "2025-01-16T09:55:42.574015Z", "shell.execute_reply": "2025-01-16T09:55:42.573701Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "assert B_Class in class_set(D_Class)" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.575593Z", "iopub.status.busy": "2025-01-16T09:55:42.575470Z", "iopub.status.idle": "2025-01-16T09:55:42.577554Z", "shell.execute_reply": "2025-01-16T09:55:42.577156Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "assert C_Class in class_set(D_Class)" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.579261Z", "iopub.status.busy": "2025-01-16T09:55:42.579118Z", "iopub.status.idle": "2025-01-16T09:55:42.581493Z", "shell.execute_reply": "2025-01-16T09:55:42.580971Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "assert D_Class in class_set(D_Class)" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.583158Z", "iopub.status.busy": "2025-01-16T09:55:42.583035Z", "iopub.status.idle": "2025-01-16T09:55:42.585471Z", "shell.execute_reply": "2025-01-16T09:55:42.585116Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{__main__.A_Class, __main__.B_Class, __main__.C_Class}" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class_set([B_Class, C_Class])" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Getting Docs" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.587241Z", "iopub.status.busy": "2025-01-16T09:55:42.587111Z", "iopub.status.idle": "2025-01-16T09:55:42.588976Z", "shell.execute_reply": "2025-01-16T09:55:42.588691Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "A_Class.__doc__" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.591266Z", "iopub.status.busy": "2025-01-16T09:55:42.591101Z", "iopub.status.idle": "2025-01-16T09:55:42.594248Z", "shell.execute_reply": "2025-01-16T09:55:42.593671Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "'A Class which does A thing right.\\n Comes with a longer docstring.'" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A_Class.__bases__[0].__doc__" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.595849Z", "iopub.status.busy": "2025-01-16T09:55:42.595750Z", "iopub.status.idle": "2025-01-16T09:55:42.598199Z", "shell.execute_reply": "2025-01-16T09:55:42.597781Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "'A_Class'" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A_Class.__bases__[0].__name__" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.600071Z", "iopub.status.busy": "2025-01-16T09:55:42.599960Z", "iopub.status.idle": "2025-01-16T09:55:42.602903Z", "shell.execute_reply": "2025-01-16T09:55:42.602471Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ " None>" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "D_Class.foo" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.604972Z", "iopub.status.busy": "2025-01-16T09:55:42.604836Z", "iopub.status.idle": "2025-01-16T09:55:42.607067Z", "shell.execute_reply": "2025-01-16T09:55:42.606661Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "D_Class.foo.__doc__" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.608796Z", "iopub.status.busy": "2025-01-16T09:55:42.608664Z", "iopub.status.idle": "2025-01-16T09:55:42.611205Z", "shell.execute_reply": "2025-01-16T09:55:42.610961Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "'The Adventures of the glorious Foo'" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A_Class.foo.__doc__" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.612929Z", "iopub.status.busy": "2025-01-16T09:55:42.612780Z", "iopub.status.idle": "2025-01-16T09:55:42.615003Z", "shell.execute_reply": "2025-01-16T09:55:42.614525Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def docstring(obj: Any) -> str:\n", " doc = inspect.getdoc(obj)\n", " return doc if doc else \"\"" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.617513Z", "iopub.status.busy": "2025-01-16T09:55:42.617381Z", "iopub.status.idle": "2025-01-16T09:55:42.619999Z", "shell.execute_reply": "2025-01-16T09:55:42.619572Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "'A Class which does A thing right.\\nComes with a longer docstring.'" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "docstring(A_Class)" ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.621864Z", "iopub.status.busy": "2025-01-16T09:55:42.621704Z", "iopub.status.idle": "2025-01-16T09:55:42.624398Z", "shell.execute_reply": "2025-01-16T09:55:42.624049Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "'A WW2 foo fighter.'" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "docstring(D_Class.foo)" ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.625905Z", "iopub.status.busy": "2025-01-16T09:55:42.625783Z", "iopub.status.idle": "2025-01-16T09:55:42.627676Z", "shell.execute_reply": "2025-01-16T09:55:42.627293Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def unknown() -> None:\n", " pass" ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.630088Z", "iopub.status.busy": "2025-01-16T09:55:42.629783Z", "iopub.status.idle": "2025-01-16T09:55:42.632415Z", "shell.execute_reply": "2025-01-16T09:55:42.632099Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "''" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "docstring(unknown)" ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.634224Z", "iopub.status.busy": "2025-01-16T09:55:42.634049Z", "iopub.status.idle": "2025-01-16T09:55:42.635874Z", "shell.execute_reply": "2025-01-16T09:55:42.635607Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import html" ] }, { "cell_type": "code", "execution_count": 37, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.637292Z", "iopub.status.busy": "2025-01-16T09:55:42.637188Z", "iopub.status.idle": "2025-01-16T09:55:42.638819Z", "shell.execute_reply": "2025-01-16T09:55:42.638514Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import re" ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.640140Z", "iopub.status.busy": "2025-01-16T09:55:42.640038Z", "iopub.status.idle": "2025-01-16T09:55:42.642249Z", "shell.execute_reply": "2025-01-16T09:55:42.642012Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def escape(text: str) -> str:\n", " text = html.escape(text)\n", " assert '<' not in text\n", " assert '>' not in text\n", " text = text.replace('{', '{')\n", " text = text.replace('|', '|')\n", " text = text.replace('}', '}')\n", " return text" ] }, { "cell_type": "code", "execution_count": 39, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.643726Z", "iopub.status.busy": "2025-01-16T09:55:42.643611Z", "iopub.status.idle": "2025-01-16T09:55:42.646296Z", "shell.execute_reply": "2025-01-16T09:55:42.645897Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "'f(foo={})'" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "escape(\"f(foo={})\")" ] }, { "cell_type": "code", "execution_count": 40, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.648202Z", "iopub.status.busy": "2025-01-16T09:55:42.648037Z", "iopub.status.idle": "2025-01-16T09:55:42.650188Z", "shell.execute_reply": "2025-01-16T09:55:42.649940Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def escape_doc(docstring: str) -> str:\n", " DOC_INDENT = 0\n", " docstring = \" \".join(\n", " ' ' * DOC_INDENT + escape(line).strip()\n", " for line in docstring.split('\\n')\n", " )\n", " return docstring" ] }, { "cell_type": "code", "execution_count": 41, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.651796Z", "iopub.status.busy": "2025-01-16T09:55:42.651642Z", "iopub.status.idle": "2025-01-16T09:55:42.653634Z", "shell.execute_reply": "2025-01-16T09:55:42.653330Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "'Hello {You|Me}'\n" ] } ], "source": [ "print(escape_doc(\"'Hello\\n {You|Me}'\"))" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "## Getting Methods and Variables" ] }, { "cell_type": "code", "execution_count": 42, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.655322Z", "iopub.status.busy": "2025-01-16T09:55:42.655177Z", "iopub.status.idle": "2025-01-16T09:55:42.659065Z", "shell.execute_reply": "2025-01-16T09:55:42.657942Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "[('VAR', 'A variable'),\n", " ('__class__', type),\n", " ('__delattr__', ),\n", " ('__dict__', mappingproxy({'__module__': '__main__', '__doc__': None})),\n", " ('__dir__', ),\n", " ('__doc__', None),\n", " ('__eq__', ),\n", " ('__format__', ),\n", " ('__ge__', ),\n", " ('__getattribute__', ),\n", " ('__getstate__', ),\n", " ('__gt__', ),\n", " ('__hash__', ),\n", " ('__init__', ),\n", " ('__init_subclass__', ),\n", " ('__le__', ),\n", " ('__lt__', ),\n", " ('__module__', '__main__'),\n", " ('__ne__', ),\n", " ('__new__', ),\n", " ('__reduce__', ),\n", " ('__reduce_ex__', ),\n", " ('__repr__', ),\n", " ('__setattr__', ),\n", " ('__sizeof__', ),\n", " ('__str__', ),\n", " ('__subclasshook__', ),\n", " ('__weakref__', ),\n", " ('bar',\n", " None>),\n", " ('foo', None>),\n", " ('quux', None>),\n", " ('qux',\n", " List[Union[int, str, NoneType]]>),\n", " ('second', None>)]" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "inspect.getmembers(D_Class)" ] }, { "cell_type": "code", "execution_count": 43, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.661084Z", "iopub.status.busy": "2025-01-16T09:55:42.660976Z", "iopub.status.idle": "2025-01-16T09:55:42.664026Z", "shell.execute_reply": "2025-01-16T09:55:42.663622Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def class_items(cls: Type, pred: Callable) -> List[Tuple[str, Any]]:\n", " def _class_items(cls: Type) -> List:\n", " all_items = inspect.getmembers(cls, pred)\n", " for base in cls.__bases__:\n", " all_items += _class_items(base)\n", "\n", " return all_items\n", "\n", " unique_items = []\n", " items_seen = set()\n", " for (name, item) in _class_items(cls):\n", " if name not in items_seen:\n", " unique_items.append((name, item))\n", " items_seen.add(name)\n", "\n", " return unique_items" ] }, { "cell_type": "code", "execution_count": 44, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.665956Z", "iopub.status.busy": "2025-01-16T09:55:42.665780Z", "iopub.status.idle": "2025-01-16T09:55:42.668120Z", "shell.execute_reply": "2025-01-16T09:55:42.667557Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def class_methods(cls: Type) -> List[Tuple[str, Callable]]:\n", " return class_items(cls, inspect.isfunction)" ] }, { "cell_type": "code", "execution_count": 45, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.670063Z", "iopub.status.busy": "2025-01-16T09:55:42.669943Z", "iopub.status.idle": "2025-01-16T09:55:42.688317Z", "shell.execute_reply": "2025-01-16T09:55:42.684514Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def defined_in(name: str, cls: Type) -> bool:\n", " if not hasattr(cls, name):\n", " return False\n", "\n", " defining_classes = []\n", "\n", " def search_superclasses(name: str, cls: Type) -> None:\n", " if not hasattr(cls, name):\n", " return\n", "\n", " for base in cls.__bases__:\n", " if hasattr(base, name):\n", " defining_classes.append(base)\n", " search_superclasses(name, base)\n", "\n", " search_superclasses(name, cls)\n", "\n", " if any(cls.__name__ != c.__name__ for c in defining_classes):\n", " return False # Already defined in superclass\n", "\n", " return True" ] }, { "cell_type": "code", "execution_count": 46, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.699102Z", "iopub.status.busy": "2025-01-16T09:55:42.698064Z", "iopub.status.idle": "2025-01-16T09:55:42.706803Z", "shell.execute_reply": "2025-01-16T09:55:42.705663Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "assert not defined_in('VAR', A_Class)" ] }, { "cell_type": "code", "execution_count": 47, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.718471Z", "iopub.status.busy": "2025-01-16T09:55:42.716982Z", "iopub.status.idle": "2025-01-16T09:55:42.720901Z", "shell.execute_reply": "2025-01-16T09:55:42.720426Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "assert defined_in('VAR', B_Class)" ] }, { "cell_type": "code", "execution_count": 48, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.725143Z", "iopub.status.busy": "2025-01-16T09:55:42.724749Z", "iopub.status.idle": "2025-01-16T09:55:42.730519Z", "shell.execute_reply": "2025-01-16T09:55:42.729472Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "assert not defined_in('VAR', C_Class)" ] }, { "cell_type": "code", "execution_count": 49, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.736350Z", "iopub.status.busy": "2025-01-16T09:55:42.736096Z", "iopub.status.idle": "2025-01-16T09:55:42.740779Z", "shell.execute_reply": "2025-01-16T09:55:42.739521Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "assert not defined_in('VAR', D_Class)" ] }, { "cell_type": "code", "execution_count": 50, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.743557Z", "iopub.status.busy": "2025-01-16T09:55:42.743333Z", "iopub.status.idle": "2025-01-16T09:55:42.746720Z", "shell.execute_reply": "2025-01-16T09:55:42.746104Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def class_vars(cls: Type) -> List[Any]:\n", " def is_var(item: Any) -> bool:\n", " return not callable(item)\n", "\n", " return [item for item in class_items(cls, is_var) \n", " if not item[0].startswith('__') and defined_in(item[0], cls)]" ] }, { "cell_type": "code", "execution_count": 51, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.750453Z", "iopub.status.busy": "2025-01-16T09:55:42.750256Z", "iopub.status.idle": "2025-01-16T09:55:42.754625Z", "shell.execute_reply": "2025-01-16T09:55:42.753854Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "[('bar',\n", " None>),\n", " ('foo', None>),\n", " ('quux', None>),\n", " ('qux',\n", " List[Union[int, str, NoneType]]>),\n", " ('second', None>)]" ] }, "execution_count": 51, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class_methods(D_Class)" ] }, { "cell_type": "code", "execution_count": 52, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.756924Z", "iopub.status.busy": "2025-01-16T09:55:42.756817Z", "iopub.status.idle": "2025-01-16T09:55:42.759899Z", "shell.execute_reply": "2025-01-16T09:55:42.759459Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "[('VAR', 'A variable')]" ] }, "execution_count": 52, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class_vars(B_Class)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "We're only interested in \n", "\n", "* functions _defined_ in that class\n", "* functions that come with a docstring" ] }, { "cell_type": "code", "execution_count": 53, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.762380Z", "iopub.status.busy": "2025-01-16T09:55:42.762213Z", "iopub.status.idle": "2025-01-16T09:55:42.765106Z", "shell.execute_reply": "2025-01-16T09:55:42.764547Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def public_class_methods(cls: Type) -> List[Tuple[str, Callable]]:\n", " return [(name, method) for (name, method) in class_methods(cls) \n", " if method.__qualname__.startswith(cls.__name__)]" ] }, { "cell_type": "code", "execution_count": 54, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.767569Z", "iopub.status.busy": "2025-01-16T09:55:42.767386Z", "iopub.status.idle": "2025-01-16T09:55:42.769822Z", "shell.execute_reply": "2025-01-16T09:55:42.769332Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def doc_class_methods(cls: Type) -> List[Tuple[str, Callable]]:\n", " return [(name, method) for (name, method) in public_class_methods(cls) \n", " if docstring(method) is not None]" ] }, { "cell_type": "code", "execution_count": 55, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.771914Z", "iopub.status.busy": "2025-01-16T09:55:42.771695Z", "iopub.status.idle": "2025-01-16T09:55:42.775649Z", "shell.execute_reply": "2025-01-16T09:55:42.775048Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "[('foo', None>)]" ] }, "execution_count": 55, "metadata": {}, "output_type": "execute_result" } ], "source": [ "public_class_methods(D_Class)" ] }, { "cell_type": "code", "execution_count": 56, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.777306Z", "iopub.status.busy": "2025-01-16T09:55:42.777198Z", "iopub.status.idle": "2025-01-16T09:55:42.780060Z", "shell.execute_reply": "2025-01-16T09:55:42.779662Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "[('foo', None>)]" ] }, "execution_count": 56, "metadata": {}, "output_type": "execute_result" } ], "source": [ "doc_class_methods(D_Class)" ] }, { "cell_type": "code", "execution_count": 57, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.782047Z", "iopub.status.busy": "2025-01-16T09:55:42.781882Z", "iopub.status.idle": "2025-01-16T09:55:42.784531Z", "shell.execute_reply": "2025-01-16T09:55:42.784278Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def overloaded_class_methods(classes: Union[Type, List[Type]]) -> Set[str]:\n", " all_methods: Dict[str, Set[Callable]] = {}\n", " for cls in class_set(classes):\n", " for (name, method) in class_methods(cls):\n", " if method.__qualname__.startswith(cls.__name__):\n", " all_methods.setdefault(name, set())\n", " all_methods[name].add(cls)\n", "\n", " return set(name for name in all_methods if len(all_methods[name]) >= 2)" ] }, { "cell_type": "code", "execution_count": 58, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.785975Z", "iopub.status.busy": "2025-01-16T09:55:42.785844Z", "iopub.status.idle": "2025-01-16T09:55:42.788456Z", "shell.execute_reply": "2025-01-16T09:55:42.788168Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "{'foo'}" ] }, "execution_count": 58, "metadata": {}, "output_type": "execute_result" } ], "source": [ "overloaded_class_methods(D_Class)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Drawing Class Hierarchy with Method Names" ] }, { "cell_type": "code", "execution_count": 59, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.789769Z", "iopub.status.busy": "2025-01-16T09:55:42.789670Z", "iopub.status.idle": "2025-01-16T09:55:42.791342Z", "shell.execute_reply": "2025-01-16T09:55:42.791082Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from inspect import signature" ] }, { "cell_type": "code", "execution_count": 60, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.792719Z", "iopub.status.busy": "2025-01-16T09:55:42.792607Z", "iopub.status.idle": "2025-01-16T09:55:42.794185Z", "shell.execute_reply": "2025-01-16T09:55:42.793927Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import warnings" ] }, { "cell_type": "code", "execution_count": 61, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.795728Z", "iopub.status.busy": "2025-01-16T09:55:42.795621Z", "iopub.status.idle": "2025-01-16T09:55:42.797549Z", "shell.execute_reply": "2025-01-16T09:55:42.797289Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import os" ] }, { "cell_type": "code", "execution_count": 62, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.798972Z", "iopub.status.busy": "2025-01-16T09:55:42.798842Z", "iopub.status.idle": "2025-01-16T09:55:42.811539Z", "shell.execute_reply": "2025-01-16T09:55:42.811158Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def display_class_hierarchy(classes: Union[Type, List[Type]], *,\n", " public_methods: Optional[List] = None,\n", " abstract_classes: Optional[List] = None,\n", " include_methods: bool = True,\n", " include_class_vars: bool = True,\n", " include_legend: bool = True,\n", " local_defs_only: bool = True,\n", " types: Dict[str, Any] = {},\n", " project: str = 'fuzzingbook',\n", " log: bool = False) -> Any:\n", " \"\"\"Visualize a class hierarchy.\n", "`classes` is a Python class (or a list of classes) to be visualized.\n", "`public_methods`, if given, is a list of methods to be shown as \"public\" (bold).\n", " (Default: all methods with a docstring)\n", "`abstract_classes`, if given, is a list of classes to be shown as \"abstract\" (cursive).\n", " (Default: all classes with an abstract method)\n", "`include_methods`: if set (default), include all methods\n", "`include_legend`: if set (default), include a legend\n", "`local_defs_only`: if set (default), hide details of imported classes\n", "`types`: type names with definitions, to be used in docs\n", " \"\"\"\n", " from graphviz import Digraph # type: ignore\n", "\n", " if project == 'debuggingbook':\n", " CLASS_FONT = 'Raleway, Helvetica, Arial, sans-serif'\n", " CLASS_COLOR = '#6A0DAD' # HTML 'purple'\n", " else:\n", " CLASS_FONT = 'Patua One, Helvetica, sans-serif'\n", " CLASS_COLOR = '#B03A2E'\n", "\n", " METHOD_FONT = \"'Fira Mono', 'Source Code Pro', 'Courier', monospace\"\n", " METHOD_COLOR = 'black'\n", "\n", " if isinstance(classes, list):\n", " starting_class = classes[0]\n", " else:\n", " starting_class = classes\n", " classes = [starting_class]\n", "\n", " title = starting_class.__name__ + \" class hierarchy\"\n", "\n", " dot = Digraph(comment=title)\n", " dot.attr('node', shape='record', fontname=CLASS_FONT)\n", " dot.attr('graph', rankdir='BT', tooltip=title)\n", " dot.attr('edge', arrowhead='empty')\n", "\n", " # Hack to force rendering as HTML, allowing hovers and links in Jupyter\n", " dot._repr_html_ = dot._repr_image_svg_xml\n", "\n", " edges = set()\n", " overloaded_methods: Set[str] = set()\n", "\n", " drawn_classes = set()\n", "\n", " def method_string(method_name: str, public: bool, overloaded: bool,\n", " fontsize: float = 10.0) -> str:\n", " method_string = f''\n", "\n", " if overloaded:\n", " name = f'{method_name}()'\n", " else:\n", " name = f'{method_name}()'\n", "\n", " if public:\n", " method_string += f'{name}'\n", " else:\n", " method_string += f'' \\\n", " f'{name}'\n", "\n", " method_string += ''\n", " return method_string\n", "\n", " def var_string(var_name: str, fontsize: int = 10) -> str:\n", " var_string = f''\n", " var_string += f'{var_name}'\n", " var_string += ''\n", " return var_string\n", "\n", " def is_overloaded(method_name: str, f: Any) -> bool:\n", " return (method_name in overloaded_methods or\n", " (docstring(f) is not None and \"in subclasses\" in docstring(f)))\n", "\n", " def is_abstract(cls: Type) -> bool:\n", " if not abstract_classes:\n", " return inspect.isabstract(cls)\n", "\n", " return (cls in abstract_classes or\n", " any(c.__name__ == cls.__name__ for c in abstract_classes))\n", "\n", " def is_public(method_name: str, f: Any) -> bool:\n", " if public_methods:\n", " return (method_name in public_methods or\n", " f in public_methods or\n", " any(f.__qualname__ == m.__qualname__\n", " for m in public_methods))\n", "\n", " return bool(docstring(f))\n", "\n", " def frame_module(frameinfo: Any) -> str:\n", " return os.path.splitext(os.path.basename(frameinfo.frame.f_code.co_filename))[0]\n", "\n", " def callers() -> List[str]:\n", " frames = inspect.getouterframes(inspect.currentframe())\n", " return [frame_module(frameinfo) for frameinfo in frames]\n", "\n", " def is_local_class(cls: Type) -> bool:\n", " return cls.__module__ == '__main__' or cls.__module__ in callers()\n", "\n", " def class_vars_string(cls: Type, url: str) -> str:\n", " cls_vars = class_vars(cls)\n", " if len(cls_vars) == 0:\n", " return \"\"\n", "\n", " vars_string = f''\n", "\n", " for (name, var) in cls_vars:\n", " if log:\n", " print(f\" Drawing {name}\")\n", "\n", " var_doc = escape(f\"{name} = {repr(var)}\")\n", " tooltip = f' tooltip=\"{var_doc}\"'\n", " href = f' href=\"{url}\"'\n", " vars_string += f''\n", "\n", " vars_string += '
'\n", "\n", " vars_string += var_string(name)\n", " vars_string += '
'\n", " return vars_string\n", "\n", " def class_methods_string(cls: Type, url: str) -> str:\n", " methods = public_class_methods(cls)\n", " # return \"
\".join([name + \"()\" for (name, f) in methods])\n", " methods_string = f''\n", "\n", " public_methods_only = local_defs_only and not is_local_class(cls)\n", "\n", " methods_seen = False\n", " for public in [True, False]:\n", " for (name, f) in methods:\n", " if public != is_public(name, f):\n", " continue\n", "\n", " if public_methods_only and not public:\n", " continue\n", "\n", " if log:\n", " print(f\" Drawing {name}()\")\n", "\n", " if is_public(name, f) and not docstring(f):\n", " warnings.warn(f\"{f.__qualname__}() is listed as public,\"\n", " f\" but has no docstring\")\n", "\n", " overloaded = is_overloaded(name, f)\n", "\n", " sig = str(inspect.signature(f))\n", " # replace 'List[Union[...]]' by the actual type def\n", " for tp in types:\n", " tp_def = str(types[tp]).replace('typing.', '')\n", " sig = sig.replace(tp_def, tp)\n", " sig = sig.replace('__main__.', '')\n", "\n", " method_doc = escape(name + sig)\n", " if docstring(f):\n", " method_doc += \": \" + escape_doc(docstring(f))\n", "\n", " if log:\n", " print(f\" Method doc: {method_doc}\")\n", "\n", " # Tooltips are only shown if a href is present, too\n", " tooltip = f' tooltip=\"{method_doc}\"'\n", " href = f' href=\"{url}\"'\n", " methods_string += f''\n", " methods_seen = True\n", "\n", " if not methods_seen:\n", " return \"\"\n", "\n", " methods_string += '
'\n", "\n", " methods_string += method_string(name, public, overloaded)\n", "\n", " methods_string += '
'\n", " return methods_string\n", "\n", " def display_class_node(cls: Type) -> None:\n", " name = cls.__name__\n", "\n", " if name in drawn_classes:\n", " return\n", " drawn_classes.add(name)\n", "\n", " if log:\n", " print(f\"Drawing class {name}\")\n", "\n", " if cls.__module__ == '__main__':\n", " url = '#'\n", " else:\n", " url = cls.__module__ + '.ipynb'\n", "\n", " if is_abstract(cls):\n", " formatted_class_name = f'{cls.__name__}'\n", " else:\n", " formatted_class_name = cls.__name__\n", "\n", " if include_methods or include_class_vars:\n", " vars = class_vars_string(cls, url)\n", " methods = class_methods_string(cls, url)\n", " spec = '<{' + \\\n", " formatted_class_name + ''\n", " if include_class_vars and vars:\n", " spec += '|' + vars\n", " if include_methods and methods:\n", " spec += '|' + methods\n", " spec += '}>'\n", " else:\n", " spec = '<' + formatted_class_name + '>'\n", "\n", " class_doc = escape('class ' + cls.__name__)\n", " if docstring(cls):\n", " class_doc += ': ' + escape_doc(docstring(cls))\n", " else:\n", " warnings.warn(f\"Class {cls.__name__} has no docstring\")\n", "\n", " dot.node(name, spec, tooltip=class_doc, href=url)\n", "\n", " def display_class_trees(trees: List[Tuple[Type, List]]) -> None:\n", " for tree in trees:\n", " (cls, subtrees) = tree\n", " display_class_node(cls)\n", "\n", " for subtree in subtrees:\n", " (subcls, _) = subtree\n", "\n", " if (cls.__name__, subcls.__name__) not in edges:\n", " dot.edge(cls.__name__, subcls.__name__)\n", " edges.add((cls.__name__, subcls.__name__))\n", "\n", " display_class_trees(subtrees)\n", "\n", " def display_legend() -> None:\n", " fontsize = 8.0\n", "\n", " label = f'Legend
' \n", "\n", " for item in [\n", " method_string(\"public_method\",\n", " public=True, overloaded=False, fontsize=fontsize),\n", " method_string(\"private_method\",\n", " public=False, overloaded=False, fontsize=fontsize),\n", " method_string(\"overloaded_method\",\n", " public=False, overloaded=True, fontsize=fontsize)\n", " ]:\n", " label += '• ' + item + '
'\n", "\n", " label += f'' \\\n", " 'Hover over names to see doc' \\\n", " '
'\n", "\n", " dot.node('Legend', label=f'<{label}>', shape='plain', fontsize=str(fontsize + 2))\n", "\n", " for cls in classes:\n", " tree = class_tree(cls)\n", " overloaded_methods = overloaded_class_methods(cls)\n", " display_class_trees(tree)\n", "\n", " if include_legend:\n", " display_legend()\n", "\n", " return dot" ] }, { "cell_type": "code", "execution_count": 63, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:42.813173Z", "iopub.status.busy": "2025-01-16T09:55:42.813043Z", "iopub.status.idle": "2025-01-16T09:55:43.793729Z", "shell.execute_reply": "2025-01-16T09:55:43.793388Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Drawing class D_Class\n", " Drawing foo()\n", " Method doc: foo(self) -> None: A WW2 foo fighter.\n", "Drawing class B_Class\n", " Drawing VAR\n", " Drawing bar()\n", " Method doc: bar(self, qux: Any = None, bartender: int = 42) -> None: A qux walks into a bar. `bartender` is an optional attribute.\n", " Drawing foo()\n", " Method doc: foo(self) -> None: A WW2 foo fighter.\n", "Drawing class A_Class\n", " Drawing foo()\n", " Method doc: foo(self) -> None: The Adventures of the glorious Foo\n", " Drawing quux()\n", " Method doc: quux(self) -> None: A method that is not used.\n", " Drawing second()\n", " Method doc: second(self) -> None\n", "Drawing class C_Class\n", " Drawing qux()\n", " Method doc: qux(self, arg: SomeType) -> SomeType\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "D_Class\n", "\n", "\n", "D_Class\n", "\n", "\n", "\n", "foo()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "B_Class\n", "\n", "\n", "B_Class\n", "\n", "\n", "\n", "VAR\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "bar()\n", "\n", "\n", "\n", "foo()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "D_Class->B_Class\n", "\n", "\n", "\n", "\n", "\n", "C_Class\n", "\n", "\n", "C_Class\n", "\n", "\n", "\n", "qux()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "D_Class->C_Class\n", "\n", "\n", "\n", "\n", "\n", "A_Class\n", "\n", "\n", "A_Class\n", "\n", "\n", "\n", "foo()\n", "\n", "\n", "\n", "quux()\n", "\n", "\n", "\n", "second()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "B_Class->A_Class\n", "\n", "\n", "\n", "\n", "\n", "Legend\n", "Legend\n", "• \n", "public_method()\n", "• \n", "private_method()\n", "• \n", "overloaded_method()\n", "Hover over names to see doc\n", "\n", "\n", "\n" ], "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "D_Class\n", "\n", "\n", "D_Class\n", "\n", "\n", "\n", "foo()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "B_Class\n", "\n", "\n", "B_Class\n", "\n", "\n", "\n", "VAR\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "bar()\n", "\n", "\n", "\n", "foo()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "D_Class->B_Class\n", "\n", "\n", "\n", "\n", "\n", "C_Class\n", "\n", "\n", "C_Class\n", "\n", "\n", "\n", "qux()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "D_Class->C_Class\n", "\n", "\n", "\n", "\n", "\n", "A_Class\n", "\n", "\n", "A_Class\n", "\n", "\n", "\n", "foo()\n", "\n", "\n", "\n", "quux()\n", "\n", "\n", "\n", "second()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "B_Class->A_Class\n", "\n", "\n", "\n", "\n", "\n", "Legend\n", "Legend\n", "• \n", "public_method()\n", "• \n", "private_method()\n", "• \n", "overloaded_method()\n", "Hover over names to see doc\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 63, "metadata": {}, "output_type": "execute_result" } ], "source": [ "display_class_hierarchy(D_Class, types={'SomeType': SomeType},\n", " project='debuggingbook', log=True)" ] }, { "cell_type": "code", "execution_count": 64, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:43.795308Z", "iopub.status.busy": "2025-01-16T09:55:43.795178Z", "iopub.status.idle": "2025-01-16T09:55:44.709024Z", "shell.execute_reply": "2025-01-16T09:55:44.708593Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "D_Class\n", "\n", "\n", "D_Class\n", "\n", "\n", "\n", "foo()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "B_Class\n", "\n", "\n", "B_Class\n", "\n", "\n", "\n", "VAR\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "bar()\n", "\n", "\n", "\n", "foo()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "D_Class->B_Class\n", "\n", "\n", "\n", "\n", "\n", "C_Class\n", "\n", "\n", "C_Class\n", "\n", "\n", "\n", "qux()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "D_Class->C_Class\n", "\n", "\n", "\n", "\n", "\n", "A_Class\n", "\n", "\n", "A_Class\n", "\n", "\n", "\n", "foo()\n", "\n", "\n", "\n", "quux()\n", "\n", "\n", "\n", "second()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "B_Class->A_Class\n", "\n", "\n", "\n", "\n", "\n", "Legend\n", "Legend\n", "• \n", "public_method()\n", "• \n", "private_method()\n", "• \n", "overloaded_method()\n", "Hover over names to see doc\n", "\n", "\n", "\n" ], "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "D_Class\n", "\n", "\n", "D_Class\n", "\n", "\n", "\n", "foo()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "B_Class\n", "\n", "\n", "B_Class\n", "\n", "\n", "\n", "VAR\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "bar()\n", "\n", "\n", "\n", "foo()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "D_Class->B_Class\n", "\n", "\n", "\n", "\n", "\n", "C_Class\n", "\n", "\n", "C_Class\n", "\n", "\n", "\n", "qux()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "D_Class->C_Class\n", "\n", "\n", "\n", "\n", "\n", "A_Class\n", "\n", "\n", "A_Class\n", "\n", "\n", "\n", "foo()\n", "\n", "\n", "\n", "quux()\n", "\n", "\n", "\n", "second()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "B_Class->A_Class\n", "\n", "\n", "\n", "\n", "\n", "Legend\n", "Legend\n", "• \n", "public_method()\n", "• \n", "private_method()\n", "• \n", "overloaded_method()\n", "Hover over names to see doc\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ "display_class_hierarchy(D_Class, types={'SomeType': SomeType},\n", " project='fuzzingbook')" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Here is a variant with abstract classes and logging:" ] }, { "cell_type": "code", "execution_count": 65, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:44.710781Z", "iopub.status.busy": "2025-01-16T09:55:44.710624Z", "iopub.status.idle": "2025-01-16T09:55:45.639686Z", "shell.execute_reply": "2025-01-16T09:55:45.639322Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Drawing class A_Class\n", " Drawing quux()\n", " Method doc: quux(self) -> None: A method that is not used.\n", " Drawing foo()\n", " Method doc: foo(self) -> None: The Adventures of the glorious Foo\n", " Drawing second()\n", " Method doc: second(self) -> None\n", "Drawing class B_Class\n", " Drawing VAR\n", " Drawing bar()\n", " Method doc: bar(self, qux: Any = None, bartender: int = 42) -> None: A qux walks into a bar. `bartender` is an optional attribute.\n", " Drawing foo()\n", " Method doc: foo(self) -> None: A WW2 foo fighter.\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "A_Class\n", "\n", "\n", "A_Class\n", "\n", "\n", "\n", "quux()\n", "\n", "\n", "\n", "foo()\n", "\n", "\n", "\n", "second()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "B_Class\n", "\n", "\n", "B_Class\n", "\n", "\n", "\n", "VAR\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "bar()\n", "\n", "\n", "\n", "foo()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "B_Class->A_Class\n", "\n", "\n", "\n", "\n", "\n", "Legend\n", "Legend\n", "• \n", "public_method()\n", "• \n", "private_method()\n", "• \n", "overloaded_method()\n", "Hover over names to see doc\n", "\n", "\n", "\n" ], "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "A_Class\n", "\n", "\n", "A_Class\n", "\n", "\n", "\n", "quux()\n", "\n", "\n", "\n", "foo()\n", "\n", "\n", "\n", "second()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "B_Class\n", "\n", "\n", "B_Class\n", "\n", "\n", "\n", "VAR\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "bar()\n", "\n", "\n", "\n", "foo()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "B_Class->A_Class\n", "\n", "\n", "\n", "\n", "\n", "Legend\n", "Legend\n", "• \n", "public_method()\n", "• \n", "private_method()\n", "• \n", "overloaded_method()\n", "Hover over names to see doc\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 65, "metadata": {}, "output_type": "execute_result" } ], "source": [ "display_class_hierarchy([A_Class, B_Class],\n", " abstract_classes=[A_Class],\n", " public_methods=[\n", " A_Class.quux,\n", " ],\n", " log=True)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Synopsis" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The function `display_class_hierarchy()` function shows the class hierarchy for the given class (or list of classes). \n", "* The keyword parameter `public_methods`, if given, is a list of \"public\" methods to be used by clients (default: all methods with docstrings).\n", "* The keyword parameter `abstract_classes`, if given, is a list of classes to be displayed as \"abstract\" (i.e. with a cursive class name)." ] }, { "cell_type": "code", "execution_count": 66, "metadata": { "execution": { "iopub.execute_input": "2025-01-16T09:55:45.641401Z", "iopub.status.busy": "2025-01-16T09:55:45.641251Z", "iopub.status.idle": "2025-01-16T09:55:47.027718Z", "shell.execute_reply": "2025-01-16T09:55:47.027159Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "D_Class\n", "\n", "\n", "D_Class\n", "\n", "\n", "\n", "foo()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "B_Class\n", "\n", "\n", "B_Class\n", "\n", "\n", "\n", "VAR\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "bar()\n", "\n", "\n", "\n", "foo()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "D_Class->B_Class\n", "\n", "\n", "\n", "\n", "\n", "C_Class\n", "\n", "\n", "C_Class\n", "\n", "\n", "\n", "qux()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "D_Class->C_Class\n", "\n", "\n", "\n", "\n", "\n", "A_Class\n", "\n", "\n", "A_Class\n", "\n", "\n", "\n", "foo()\n", "\n", "\n", "\n", "quux()\n", "\n", "\n", "\n", "second()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "B_Class->A_Class\n", "\n", "\n", "\n", "\n", "\n", "Legend\n", "Legend\n", "• \n", "public_method()\n", "• \n", "private_method()\n", "• \n", "overloaded_method()\n", "Hover over names to see doc\n", "\n", "\n", "\n" ], "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "D_Class\n", "\n", "\n", "D_Class\n", "\n", "\n", "\n", "foo()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "B_Class\n", "\n", "\n", "B_Class\n", "\n", "\n", "\n", "VAR\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "bar()\n", "\n", "\n", "\n", "foo()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "D_Class->B_Class\n", "\n", "\n", "\n", "\n", "\n", "C_Class\n", "\n", "\n", "C_Class\n", "\n", "\n", "\n", "qux()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "D_Class->C_Class\n", "\n", "\n", "\n", "\n", "\n", "A_Class\n", "\n", "\n", "A_Class\n", "\n", "\n", "\n", "foo()\n", "\n", "\n", "\n", "quux()\n", "\n", "\n", "\n", "second()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "B_Class->A_Class\n", "\n", "\n", "\n", "\n", "\n", "Legend\n", "Legend\n", "• \n", "public_method()\n", "• \n", "private_method()\n", "• \n", "overloaded_method()\n", "Hover over names to see doc\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 66, "metadata": {}, "output_type": "execute_result" } ], "source": [ "display_class_hierarchy(D_Class, abstract_classes=[A_Class])" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Exercises" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Enjoy!" ] } ], "metadata": { "ipub": { "bibliography": "fuzzingbook.bib", "toc": true }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.8" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": true, "title_cell": "", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": true, "toc_window_display": true }, "toc-autonumbering": false }, "nbformat": 4, "nbformat_minor": 4 }