{ "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": "2023-11-12T13:02:49.656049Z", "iopub.status.busy": "2023-11-12T13:02:49.655947Z", "iopub.status.idle": "2023-11-12T13:02:49.686067Z", "shell.execute_reply": "2023-11-12T13:02:49.685772Z" }, "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": "2023-11-12T13:02:49.688035Z", "iopub.status.busy": "2023-11-12T13:02:49.687872Z", "iopub.status.idle": "2023-11-12T13:02:49.689606Z", "shell.execute_reply": "2023-11-12T13:02:49.689342Z" }, "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": "2023-11-12T13:02:49.691208Z", "iopub.status.busy": "2023-11-12T13:02:49.691095Z", "iopub.status.idle": "2023-11-12T13:02:49.692845Z", "shell.execute_reply": "2023-11-12T13:02:49.692583Z" }, "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": "2023-11-12T13:02:49.694287Z", "iopub.status.busy": "2023-11-12T13:02:49.694182Z", "iopub.status.idle": "2023-11-12T13:02:49.696264Z", "shell.execute_reply": "2023-11-12T13:02:49.696023Z" }, "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": "2023-11-12T13:02:49.697777Z", "iopub.status.busy": "2023-11-12T13:02:49.697667Z", "iopub.status.idle": "2023-11-12T13:02:49.699610Z", "shell.execute_reply": "2023-11-12T13:02:49.699349Z" }, "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": "2023-11-12T13:02:49.701218Z", "iopub.status.busy": "2023-11-12T13:02:49.701106Z", "iopub.status.idle": "2023-11-12T13:02:49.702910Z", "shell.execute_reply": "2023-11-12T13:02:49.702642Z" }, "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": "2023-11-12T13:02:49.704465Z", "iopub.status.busy": "2023-11-12T13:02:49.704363Z", "iopub.status.idle": "2023-11-12T13:02:49.706347Z", "shell.execute_reply": "2023-11-12T13:02:49.706108Z" }, "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": "2023-11-12T13:02:49.707889Z", "iopub.status.busy": "2023-11-12T13:02:49.707787Z", "iopub.status.idle": "2023-11-12T13:02:49.709494Z", "shell.execute_reply": "2023-11-12T13:02:49.709232Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "SomeType = List[Optional[Union[str, int]]]" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T13:02:49.711031Z", "iopub.status.busy": "2023-11-12T13:02:49.710922Z", "iopub.status.idle": "2023-11-12T13:02:49.712602Z", "shell.execute_reply": "2023-11-12T13:02:49.712350Z" }, "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": "2023-11-12T13:02:49.714167Z", "iopub.status.busy": "2023-11-12T13:02:49.714063Z", "iopub.status.idle": "2023-11-12T13:02:49.715854Z", "shell.execute_reply": "2023-11-12T13:02:49.715419Z" }, "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": "2023-11-12T13:02:49.717417Z", "iopub.status.busy": "2023-11-12T13:02:49.717308Z", "iopub.status.idle": "2023-11-12T13:02:49.718971Z", "shell.execute_reply": "2023-11-12T13:02:49.718719Z" }, "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": "2023-11-12T13:02:49.720484Z", "iopub.status.busy": "2023-11-12T13:02:49.720381Z", "iopub.status.idle": "2023-11-12T13:02:49.723568Z", "shell.execute_reply": "2023-11-12T13:02:49.723271Z" }, "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": "2023-11-12T13:02:49.743896Z", "iopub.status.busy": "2023-11-12T13:02:49.743754Z", "iopub.status.idle": "2023-11-12T13:02:49.745851Z", "shell.execute_reply": "2023-11-12T13:02:49.745586Z" }, "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": "2023-11-12T13:02:49.747357Z", "iopub.status.busy": "2023-11-12T13:02:49.747251Z", "iopub.status.idle": "2023-11-12T13:02:49.749636Z", "shell.execute_reply": "2023-11-12T13:02:49.749365Z" }, "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": "2023-11-12T13:02:49.751220Z", "iopub.status.busy": "2023-11-12T13:02:49.751110Z", "iopub.status.idle": "2023-11-12T13:02:49.753230Z", "shell.execute_reply": "2023-11-12T13:02:49.752973Z" }, "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": "2023-11-12T13:02:49.754796Z", "iopub.status.busy": "2023-11-12T13:02:49.754685Z", "iopub.status.idle": "2023-11-12T13:02:49.756732Z", "shell.execute_reply": "2023-11-12T13:02:49.756481Z" }, "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": "2023-11-12T13:02:49.758239Z", "iopub.status.busy": "2023-11-12T13:02:49.758128Z", "iopub.status.idle": "2023-11-12T13:02:49.759787Z", "shell.execute_reply": "2023-11-12T13:02:49.759540Z" }, "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": "2023-11-12T13:02:49.761459Z", "iopub.status.busy": "2023-11-12T13:02:49.761358Z", "iopub.status.idle": "2023-11-12T13:02:49.763748Z", "shell.execute_reply": "2023-11-12T13:02:49.763474Z" }, "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": "2023-11-12T13:02:49.765214Z", "iopub.status.busy": "2023-11-12T13:02:49.765114Z", "iopub.status.idle": "2023-11-12T13:02:49.767102Z", "shell.execute_reply": "2023-11-12T13:02:49.766831Z" }, "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": "2023-11-12T13:02:49.768621Z", "iopub.status.busy": "2023-11-12T13:02:49.768514Z", "iopub.status.idle": "2023-11-12T13:02:49.770075Z", "shell.execute_reply": "2023-11-12T13:02:49.769829Z" }, "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": "2023-11-12T13:02:49.771525Z", "iopub.status.busy": "2023-11-12T13:02:49.771442Z", "iopub.status.idle": "2023-11-12T13:02:49.773031Z", "shell.execute_reply": "2023-11-12T13:02:49.772802Z" }, "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": "2023-11-12T13:02:49.774467Z", "iopub.status.busy": "2023-11-12T13:02:49.774385Z", "iopub.status.idle": "2023-11-12T13:02:49.776204Z", "shell.execute_reply": "2023-11-12T13:02:49.775960Z" }, "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": "2023-11-12T13:02:49.777608Z", "iopub.status.busy": "2023-11-12T13:02:49.777526Z", "iopub.status.idle": "2023-11-12T13:02:49.779169Z", "shell.execute_reply": "2023-11-12T13:02:49.778947Z" }, "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": "2023-11-12T13:02:49.780536Z", "iopub.status.busy": "2023-11-12T13:02:49.780447Z", "iopub.status.idle": "2023-11-12T13:02:49.782572Z", "shell.execute_reply": "2023-11-12T13:02:49.782296Z" }, "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": "2023-11-12T13:02:49.784001Z", "iopub.status.busy": "2023-11-12T13:02:49.783918Z", "iopub.status.idle": "2023-11-12T13:02:49.785445Z", "shell.execute_reply": "2023-11-12T13:02:49.785201Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "A_Class.__doc__" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T13:02:49.786870Z", "iopub.status.busy": "2023-11-12T13:02:49.786784Z", "iopub.status.idle": "2023-11-12T13:02:49.788983Z", "shell.execute_reply": "2023-11-12T13:02:49.788731Z" }, "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": "2023-11-12T13:02:49.790529Z", "iopub.status.busy": "2023-11-12T13:02:49.790421Z", "iopub.status.idle": "2023-11-12T13:02:49.792307Z", "shell.execute_reply": "2023-11-12T13:02:49.792067Z" }, "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": "2023-11-12T13:02:49.793696Z", "iopub.status.busy": "2023-11-12T13:02:49.793598Z", "iopub.status.idle": "2023-11-12T13:02:49.795587Z", "shell.execute_reply": "2023-11-12T13:02:49.795316Z" }, "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": "2023-11-12T13:02:49.797069Z", "iopub.status.busy": "2023-11-12T13:02:49.796966Z", "iopub.status.idle": "2023-11-12T13:02:49.798524Z", "shell.execute_reply": "2023-11-12T13:02:49.798271Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "D_Class.foo.__doc__" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T13:02:49.799924Z", "iopub.status.busy": "2023-11-12T13:02:49.799845Z", "iopub.status.idle": "2023-11-12T13:02:49.801887Z", "shell.execute_reply": "2023-11-12T13:02:49.801636Z" }, "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": "2023-11-12T13:02:49.803479Z", "iopub.status.busy": "2023-11-12T13:02:49.803379Z", "iopub.status.idle": "2023-11-12T13:02:49.805028Z", "shell.execute_reply": "2023-11-12T13:02:49.804799Z" }, "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": "2023-11-12T13:02:49.806536Z", "iopub.status.busy": "2023-11-12T13:02:49.806438Z", "iopub.status.idle": "2023-11-12T13:02:49.808374Z", "shell.execute_reply": "2023-11-12T13:02:49.808141Z" }, "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": "2023-11-12T13:02:49.809920Z", "iopub.status.busy": "2023-11-12T13:02:49.809805Z", "iopub.status.idle": "2023-11-12T13:02:49.811732Z", "shell.execute_reply": "2023-11-12T13:02:49.811475Z" }, "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": "2023-11-12T13:02:49.813199Z", "iopub.status.busy": "2023-11-12T13:02:49.813093Z", "iopub.status.idle": "2023-11-12T13:02:49.814713Z", "shell.execute_reply": "2023-11-12T13:02:49.814466Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def unknown() -> None:\n", " pass" ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T13:02:49.816174Z", "iopub.status.busy": "2023-11-12T13:02:49.816088Z", "iopub.status.idle": "2023-11-12T13:02:49.818226Z", "shell.execute_reply": "2023-11-12T13:02:49.817959Z" }, "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": "2023-11-12T13:02:49.819675Z", "iopub.status.busy": "2023-11-12T13:02:49.819575Z", "iopub.status.idle": "2023-11-12T13:02:49.821103Z", "shell.execute_reply": "2023-11-12T13:02:49.820865Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import html" ] }, { "cell_type": "code", "execution_count": 37, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T13:02:49.822460Z", "iopub.status.busy": "2023-11-12T13:02:49.822377Z", "iopub.status.idle": "2023-11-12T13:02:49.824039Z", "shell.execute_reply": "2023-11-12T13:02:49.823779Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import re" ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T13:02:49.825426Z", "iopub.status.busy": "2023-11-12T13:02:49.825344Z", "iopub.status.idle": "2023-11-12T13:02:49.827500Z", "shell.execute_reply": "2023-11-12T13:02:49.827234Z" }, "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": "2023-11-12T13:02:49.829003Z", "iopub.status.busy": "2023-11-12T13:02:49.828915Z", "iopub.status.idle": "2023-11-12T13:02:49.831112Z", "shell.execute_reply": "2023-11-12T13:02:49.830847Z" }, "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": "2023-11-12T13:02:49.832586Z", "iopub.status.busy": "2023-11-12T13:02:49.832476Z", "iopub.status.idle": "2023-11-12T13:02:49.834359Z", "shell.execute_reply": "2023-11-12T13:02:49.834119Z" }, "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": "2023-11-12T13:02:49.835827Z", "iopub.status.busy": "2023-11-12T13:02:49.835723Z", "iopub.status.idle": "2023-11-12T13:02:49.837387Z", "shell.execute_reply": "2023-11-12T13:02:49.837126Z" }, "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": "2023-11-12T13:02:49.838900Z", "iopub.status.busy": "2023-11-12T13:02:49.838795Z", "iopub.status.idle": "2023-11-12T13:02:49.841777Z", "shell.execute_reply": "2023-11-12T13:02:49.841521Z" }, "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", " ('__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[str, int, 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": "2023-11-12T13:02:49.843295Z", "iopub.status.busy": "2023-11-12T13:02:49.843190Z", "iopub.status.idle": "2023-11-12T13:02:49.845572Z", "shell.execute_reply": "2023-11-12T13:02:49.845335Z" }, "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": "2023-11-12T13:02:49.847115Z", "iopub.status.busy": "2023-11-12T13:02:49.847013Z", "iopub.status.idle": "2023-11-12T13:02:49.848780Z", "shell.execute_reply": "2023-11-12T13:02:49.848509Z" }, "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": "2023-11-12T13:02:49.850331Z", "iopub.status.busy": "2023-11-12T13:02:49.850218Z", "iopub.status.idle": "2023-11-12T13:02:49.853136Z", "shell.execute_reply": "2023-11-12T13:02:49.852846Z" }, "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": "2023-11-12T13:02:49.854675Z", "iopub.status.busy": "2023-11-12T13:02:49.854557Z", "iopub.status.idle": "2023-11-12T13:02:49.856103Z", "shell.execute_reply": "2023-11-12T13:02:49.855867Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "assert not defined_in('VAR', A_Class)" ] }, { "cell_type": "code", "execution_count": 47, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T13:02:49.857454Z", "iopub.status.busy": "2023-11-12T13:02:49.857372Z", "iopub.status.idle": "2023-11-12T13:02:49.859220Z", "shell.execute_reply": "2023-11-12T13:02:49.858956Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "assert defined_in('VAR', B_Class)" ] }, { "cell_type": "code", "execution_count": 48, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T13:02:49.860703Z", "iopub.status.busy": "2023-11-12T13:02:49.860612Z", "iopub.status.idle": "2023-11-12T13:02:49.862319Z", "shell.execute_reply": "2023-11-12T13:02:49.862063Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "assert not defined_in('VAR', C_Class)" ] }, { "cell_type": "code", "execution_count": 49, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T13:02:49.863788Z", "iopub.status.busy": "2023-11-12T13:02:49.863703Z", "iopub.status.idle": "2023-11-12T13:02:49.865192Z", "shell.execute_reply": "2023-11-12T13:02:49.864929Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "assert not defined_in('VAR', D_Class)" ] }, { "cell_type": "code", "execution_count": 50, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T13:02:49.866724Z", "iopub.status.busy": "2023-11-12T13:02:49.866632Z", "iopub.status.idle": "2023-11-12T13:02:49.868697Z", "shell.execute_reply": "2023-11-12T13:02:49.868460Z" }, "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": "2023-11-12T13:02:49.870288Z", "iopub.status.busy": "2023-11-12T13:02:49.870202Z", "iopub.status.idle": "2023-11-12T13:02:49.872757Z", "shell.execute_reply": "2023-11-12T13:02:49.872499Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "[('bar',\n", " None>),\n", " ('foo', None>),\n", " ('quux', None>),\n", " ('qux',\n", " List[Union[str, int, 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": "2023-11-12T13:02:49.874386Z", "iopub.status.busy": "2023-11-12T13:02:49.874280Z", "iopub.status.idle": "2023-11-12T13:02:49.876365Z", "shell.execute_reply": "2023-11-12T13:02:49.876113Z" }, "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": "2023-11-12T13:02:49.877903Z", "iopub.status.busy": "2023-11-12T13:02:49.877804Z", "iopub.status.idle": "2023-11-12T13:02:49.879644Z", "shell.execute_reply": "2023-11-12T13:02:49.879397Z" }, "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": "2023-11-12T13:02:49.881039Z", "iopub.status.busy": "2023-11-12T13:02:49.880930Z", "iopub.status.idle": "2023-11-12T13:02:49.882814Z", "shell.execute_reply": "2023-11-12T13:02:49.882534Z" }, "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": "2023-11-12T13:02:49.884407Z", "iopub.status.busy": "2023-11-12T13:02:49.884297Z", "iopub.status.idle": "2023-11-12T13:02:49.886426Z", "shell.execute_reply": "2023-11-12T13:02:49.886181Z" }, "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": "2023-11-12T13:02:49.887939Z", "iopub.status.busy": "2023-11-12T13:02:49.887820Z", "iopub.status.idle": "2023-11-12T13:02:49.889915Z", "shell.execute_reply": "2023-11-12T13:02:49.889658Z" }, "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": "2023-11-12T13:02:49.891311Z", "iopub.status.busy": "2023-11-12T13:02:49.891212Z", "iopub.status.idle": "2023-11-12T13:02:49.893523Z", "shell.execute_reply": "2023-11-12T13:02:49.893280Z" }, "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": "2023-11-12T13:02:49.894985Z", "iopub.status.busy": "2023-11-12T13:02:49.894879Z", "iopub.status.idle": "2023-11-12T13:02:49.897206Z", "shell.execute_reply": "2023-11-12T13:02:49.896949Z" }, "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": "2023-11-12T13:02:49.898762Z", "iopub.status.busy": "2023-11-12T13:02:49.898645Z", "iopub.status.idle": "2023-11-12T13:02:49.900225Z", "shell.execute_reply": "2023-11-12T13:02:49.899984Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from inspect import signature" ] }, { "cell_type": "code", "execution_count": 60, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T13:02:49.901675Z", "iopub.status.busy": "2023-11-12T13:02:49.901594Z", "iopub.status.idle": "2023-11-12T13:02:49.903100Z", "shell.execute_reply": "2023-11-12T13:02:49.902849Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import warnings" ] }, { "cell_type": "code", "execution_count": 61, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T13:02:49.904697Z", "iopub.status.busy": "2023-11-12T13:02:49.904615Z", "iopub.status.idle": "2023-11-12T13:02:49.906235Z", "shell.execute_reply": "2023-11-12T13:02:49.905982Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import os" ] }, { "cell_type": "code", "execution_count": 62, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T13:02:49.907885Z", "iopub.status.busy": "2023-11-12T13:02:49.907767Z", "iopub.status.idle": "2023-11-12T13:02:49.935346Z", "shell.execute_reply": "2023-11-12T13:02:49.935073Z" }, "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", " 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": "2023-11-12T13:02:49.936995Z", "iopub.status.busy": "2023-11-12T13:02:49.936898Z", "iopub.status.idle": "2023-11-12T13:02:50.354450Z", "shell.execute_reply": "2023-11-12T13:02:50.354097Z" }, "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/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": "2023-11-12T13:02:50.356140Z", "iopub.status.busy": "2023-11-12T13:02:50.356017Z", "iopub.status.idle": "2023-11-12T13:02:50.754637Z", "shell.execute_reply": "2023-11-12T13:02:50.754273Z" }, "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/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": "2023-11-12T13:02:50.756511Z", "iopub.status.busy": "2023-11-12T13:02:50.756391Z", "iopub.status.idle": "2023-11-12T13:02:51.146669Z", "shell.execute_reply": "2023-11-12T13:02:51.146281Z" }, "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/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": "2023-11-12T13:02:51.148508Z", "iopub.status.busy": "2023-11-12T13:02:51.148352Z", "iopub.status.idle": "2023-11-12T13:02:51.548552Z", "shell.execute_reply": "2023-11-12T13:02:51.548165Z" }, "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/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.10.2" }, "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 }