{ "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": "2024-04-27T13:59:35.562164Z", "iopub.status.busy": "2024-04-27T13:59:35.562019Z", "iopub.status.idle": "2024-04-27T13:59:35.608897Z", "shell.execute_reply": "2024-04-27T13:59:35.608536Z" }, "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 fuzzingbook.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": "2024-04-27T13:59:35.611022Z", "iopub.status.busy": "2024-04-27T13:59:35.610871Z", "iopub.status.idle": "2024-04-27T13:59:35.612614Z", "shell.execute_reply": "2024-04-27T13:59:35.612354Z" }, "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": "2024-04-27T13:59:35.614410Z", "iopub.status.busy": "2024-04-27T13:59:35.614261Z", "iopub.status.idle": "2024-04-27T13:59:35.616379Z", "shell.execute_reply": "2024-04-27T13:59:35.616079Z" }, "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": "2024-04-27T13:59:35.617851Z", "iopub.status.busy": "2024-04-27T13:59:35.617741Z", "iopub.status.idle": "2024-04-27T13:59:35.619949Z", "shell.execute_reply": "2024-04-27T13:59:35.619692Z" }, "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": "2024-04-27T13:59:35.621570Z", "iopub.status.busy": "2024-04-27T13:59:35.621358Z", "iopub.status.idle": "2024-04-27T13:59:35.623609Z", "shell.execute_reply": "2024-04-27T13:59:35.623281Z" }, "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": "2024-04-27T13:59:35.625049Z", "iopub.status.busy": "2024-04-27T13:59:35.624943Z", "iopub.status.idle": "2024-04-27T13:59:35.626562Z", "shell.execute_reply": "2024-04-27T13:59:35.626324Z" }, "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": "2024-04-27T13:59:35.628028Z", "iopub.status.busy": "2024-04-27T13:59:35.627939Z", "iopub.status.idle": "2024-04-27T13:59:35.629903Z", "shell.execute_reply": "2024-04-27T13:59:35.629654Z" }, "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": "2024-04-27T13:59:35.631245Z", "iopub.status.busy": "2024-04-27T13:59:35.631145Z", "iopub.status.idle": "2024-04-27T13:59:35.632795Z", "shell.execute_reply": "2024-04-27T13:59:35.632551Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "SomeType = List[Optional[Union[str, int]]]" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2024-04-27T13:59:35.634452Z", "iopub.status.busy": "2024-04-27T13:59:35.634294Z", "iopub.status.idle": "2024-04-27T13:59:35.636127Z", "shell.execute_reply": "2024-04-27T13:59:35.635853Z" }, "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": "2024-04-27T13:59:35.637707Z", "iopub.status.busy": "2024-04-27T13:59:35.637564Z", "iopub.status.idle": "2024-04-27T13:59:35.639509Z", "shell.execute_reply": "2024-04-27T13:59:35.639227Z" }, "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": "2024-04-27T13:59:35.640893Z", "iopub.status.busy": "2024-04-27T13:59:35.640792Z", "iopub.status.idle": "2024-04-27T13:59:35.642418Z", "shell.execute_reply": "2024-04-27T13:59:35.642171Z" }, "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": "2024-04-27T13:59:35.643826Z", "iopub.status.busy": "2024-04-27T13:59:35.643740Z", "iopub.status.idle": "2024-04-27T13:59:35.647039Z", "shell.execute_reply": "2024-04-27T13:59:35.646723Z" }, "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": "2024-04-27T13:59:35.668591Z", "iopub.status.busy": "2024-04-27T13:59:35.668451Z", "iopub.status.idle": "2024-04-27T13:59:35.670648Z", "shell.execute_reply": "2024-04-27T13:59:35.670369Z" }, "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": "2024-04-27T13:59:35.672147Z", "iopub.status.busy": "2024-04-27T13:59:35.672038Z", "iopub.status.idle": "2024-04-27T13:59:35.674398Z", "shell.execute_reply": "2024-04-27T13:59:35.674153Z" }, "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": "2024-04-27T13:59:35.675757Z", "iopub.status.busy": "2024-04-27T13:59:35.675651Z", "iopub.status.idle": "2024-04-27T13:59:35.677740Z", "shell.execute_reply": "2024-04-27T13:59:35.677492Z" }, "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": "2024-04-27T13:59:35.679156Z", "iopub.status.busy": "2024-04-27T13:59:35.679059Z", "iopub.status.idle": "2024-04-27T13:59:35.681037Z", "shell.execute_reply": "2024-04-27T13:59:35.680801Z" }, "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": "2024-04-27T13:59:35.682709Z", "iopub.status.busy": "2024-04-27T13:59:35.682476Z", "iopub.status.idle": "2024-04-27T13:59:35.684217Z", "shell.execute_reply": "2024-04-27T13:59:35.683900Z" }, "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": "2024-04-27T13:59:35.685780Z", "iopub.status.busy": "2024-04-27T13:59:35.685664Z", "iopub.status.idle": "2024-04-27T13:59:35.688230Z", "shell.execute_reply": "2024-04-27T13:59:35.687779Z" }, "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": "2024-04-27T13:59:35.690059Z", "iopub.status.busy": "2024-04-27T13:59:35.689928Z", "iopub.status.idle": "2024-04-27T13:59:35.692379Z", "shell.execute_reply": "2024-04-27T13:59:35.692032Z" }, "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": "2024-04-27T13:59:35.694320Z", "iopub.status.busy": "2024-04-27T13:59:35.694161Z", "iopub.status.idle": "2024-04-27T13:59:35.696022Z", "shell.execute_reply": "2024-04-27T13:59:35.695743Z" }, "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": "2024-04-27T13:59:35.697480Z", "iopub.status.busy": "2024-04-27T13:59:35.697367Z", "iopub.status.idle": "2024-04-27T13:59:35.698955Z", "shell.execute_reply": "2024-04-27T13:59:35.698718Z" }, "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": "2024-04-27T13:59:35.700384Z", "iopub.status.busy": "2024-04-27T13:59:35.700297Z", "iopub.status.idle": "2024-04-27T13:59:35.702113Z", "shell.execute_reply": "2024-04-27T13:59:35.701864Z" }, "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": "2024-04-27T13:59:35.704112Z", "iopub.status.busy": "2024-04-27T13:59:35.703954Z", "iopub.status.idle": "2024-04-27T13:59:35.705679Z", "shell.execute_reply": "2024-04-27T13:59:35.705415Z" }, "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": "2024-04-27T13:59:35.707248Z", "iopub.status.busy": "2024-04-27T13:59:35.707148Z", "iopub.status.idle": "2024-04-27T13:59:35.709423Z", "shell.execute_reply": "2024-04-27T13:59:35.709032Z" }, "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": "2024-04-27T13:59:35.710867Z", "iopub.status.busy": "2024-04-27T13:59:35.710762Z", "iopub.status.idle": "2024-04-27T13:59:35.712454Z", "shell.execute_reply": "2024-04-27T13:59:35.712153Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "A_Class.__doc__" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "execution": { "iopub.execute_input": "2024-04-27T13:59:35.714178Z", "iopub.status.busy": "2024-04-27T13:59:35.714054Z", "iopub.status.idle": "2024-04-27T13:59:35.716726Z", "shell.execute_reply": "2024-04-27T13:59:35.716417Z" }, "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": "2024-04-27T13:59:35.718647Z", "iopub.status.busy": "2024-04-27T13:59:35.718531Z", "iopub.status.idle": "2024-04-27T13:59:35.721014Z", "shell.execute_reply": "2024-04-27T13:59:35.720751Z" }, "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": "2024-04-27T13:59:35.722504Z", "iopub.status.busy": "2024-04-27T13:59:35.722394Z", "iopub.status.idle": "2024-04-27T13:59:35.724534Z", "shell.execute_reply": "2024-04-27T13:59:35.724271Z" }, "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": "2024-04-27T13:59:35.726198Z", "iopub.status.busy": "2024-04-27T13:59:35.726057Z", "iopub.status.idle": "2024-04-27T13:59:35.727867Z", "shell.execute_reply": "2024-04-27T13:59:35.727528Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "D_Class.foo.__doc__" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "execution": { "iopub.execute_input": "2024-04-27T13:59:35.729960Z", "iopub.status.busy": "2024-04-27T13:59:35.729811Z", "iopub.status.idle": "2024-04-27T13:59:35.731987Z", "shell.execute_reply": "2024-04-27T13:59:35.731707Z" }, "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": "2024-04-27T13:59:35.733787Z", "iopub.status.busy": "2024-04-27T13:59:35.733657Z", "iopub.status.idle": "2024-04-27T13:59:35.735800Z", "shell.execute_reply": "2024-04-27T13:59:35.735433Z" }, "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": "2024-04-27T13:59:35.737426Z", "iopub.status.busy": "2024-04-27T13:59:35.737309Z", "iopub.status.idle": "2024-04-27T13:59:35.739674Z", "shell.execute_reply": "2024-04-27T13:59:35.739339Z" }, "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": "2024-04-27T13:59:35.741467Z", "iopub.status.busy": "2024-04-27T13:59:35.741345Z", "iopub.status.idle": "2024-04-27T13:59:35.743649Z", "shell.execute_reply": "2024-04-27T13:59:35.743361Z" }, "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": "2024-04-27T13:59:35.745169Z", "iopub.status.busy": "2024-04-27T13:59:35.745056Z", "iopub.status.idle": "2024-04-27T13:59:35.746780Z", "shell.execute_reply": "2024-04-27T13:59:35.746457Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def unknown() -> None:\n", " pass" ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "execution": { "iopub.execute_input": "2024-04-27T13:59:35.748873Z", "iopub.status.busy": "2024-04-27T13:59:35.748654Z", "iopub.status.idle": "2024-04-27T13:59:35.751279Z", "shell.execute_reply": "2024-04-27T13:59:35.750903Z" }, "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": "2024-04-27T13:59:35.752929Z", "iopub.status.busy": "2024-04-27T13:59:35.752796Z", "iopub.status.idle": "2024-04-27T13:59:35.754391Z", "shell.execute_reply": "2024-04-27T13:59:35.754148Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import html" ] }, { "cell_type": "code", "execution_count": 37, "metadata": { "execution": { "iopub.execute_input": "2024-04-27T13:59:35.755862Z", "iopub.status.busy": "2024-04-27T13:59:35.755773Z", "iopub.status.idle": "2024-04-27T13:59:35.757394Z", "shell.execute_reply": "2024-04-27T13:59:35.757113Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import re" ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "execution": { "iopub.execute_input": "2024-04-27T13:59:35.759333Z", "iopub.status.busy": "2024-04-27T13:59:35.759188Z", "iopub.status.idle": "2024-04-27T13:59:35.761539Z", "shell.execute_reply": "2024-04-27T13:59:35.761122Z" }, "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": "2024-04-27T13:59:35.763470Z", "iopub.status.busy": "2024-04-27T13:59:35.763150Z", "iopub.status.idle": "2024-04-27T13:59:35.765710Z", "shell.execute_reply": "2024-04-27T13:59:35.765399Z" }, "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": "2024-04-27T13:59:35.767554Z", "iopub.status.busy": "2024-04-27T13:59:35.767324Z", "iopub.status.idle": "2024-04-27T13:59:35.769681Z", "shell.execute_reply": "2024-04-27T13:59:35.769363Z" }, "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": "2024-04-27T13:59:35.771466Z", "iopub.status.busy": "2024-04-27T13:59:35.771336Z", "iopub.status.idle": "2024-04-27T13:59:35.773507Z", "shell.execute_reply": "2024-04-27T13:59:35.773185Z" }, "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": "2024-04-27T13:59:35.775176Z", "iopub.status.busy": "2024-04-27T13:59:35.775031Z", "iopub.status.idle": "2024-04-27T13:59:35.778746Z", "shell.execute_reply": "2024-04-27T13:59:35.778441Z" }, "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": "2024-04-27T13:59:35.780289Z", "iopub.status.busy": "2024-04-27T13:59:35.780203Z", "iopub.status.idle": "2024-04-27T13:59:35.783064Z", "shell.execute_reply": "2024-04-27T13:59:35.782785Z" }, "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": "2024-04-27T13:59:35.784713Z", "iopub.status.busy": "2024-04-27T13:59:35.784619Z", "iopub.status.idle": "2024-04-27T13:59:35.786569Z", "shell.execute_reply": "2024-04-27T13:59:35.786288Z" }, "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": "2024-04-27T13:59:35.788306Z", "iopub.status.busy": "2024-04-27T13:59:35.788159Z", "iopub.status.idle": "2024-04-27T13:59:35.791015Z", "shell.execute_reply": "2024-04-27T13:59:35.790703Z" }, "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": "2024-04-27T13:59:35.792641Z", "iopub.status.busy": "2024-04-27T13:59:35.792457Z", "iopub.status.idle": "2024-04-27T13:59:35.794707Z", "shell.execute_reply": "2024-04-27T13:59:35.794097Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "assert not defined_in('VAR', A_Class)" ] }, { "cell_type": "code", "execution_count": 47, "metadata": { "execution": { "iopub.execute_input": "2024-04-27T13:59:35.796636Z", "iopub.status.busy": "2024-04-27T13:59:35.796479Z", "iopub.status.idle": "2024-04-27T13:59:35.798461Z", "shell.execute_reply": "2024-04-27T13:59:35.798206Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "assert defined_in('VAR', B_Class)" ] }, { "cell_type": "code", "execution_count": 48, "metadata": { "execution": { "iopub.execute_input": "2024-04-27T13:59:35.800176Z", "iopub.status.busy": "2024-04-27T13:59:35.800029Z", "iopub.status.idle": "2024-04-27T13:59:35.801872Z", "shell.execute_reply": "2024-04-27T13:59:35.801574Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "assert not defined_in('VAR', C_Class)" ] }, { "cell_type": "code", "execution_count": 49, "metadata": { "execution": { "iopub.execute_input": "2024-04-27T13:59:35.803700Z", "iopub.status.busy": "2024-04-27T13:59:35.803530Z", "iopub.status.idle": "2024-04-27T13:59:35.805604Z", "shell.execute_reply": "2024-04-27T13:59:35.805245Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "assert not defined_in('VAR', D_Class)" ] }, { "cell_type": "code", "execution_count": 50, "metadata": { "execution": { "iopub.execute_input": "2024-04-27T13:59:35.807236Z", "iopub.status.busy": "2024-04-27T13:59:35.807119Z", "iopub.status.idle": "2024-04-27T13:59:35.809191Z", "shell.execute_reply": "2024-04-27T13:59:35.808935Z" }, "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": "2024-04-27T13:59:35.810683Z", "iopub.status.busy": "2024-04-27T13:59:35.810573Z", "iopub.status.idle": "2024-04-27T13:59:35.813321Z", "shell.execute_reply": "2024-04-27T13:59:35.812872Z" }, "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": "2024-04-27T13:59:35.815289Z", "iopub.status.busy": "2024-04-27T13:59:35.815153Z", "iopub.status.idle": "2024-04-27T13:59:35.817778Z", "shell.execute_reply": "2024-04-27T13:59:35.817378Z" }, "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": "2024-04-27T13:59:35.819361Z", "iopub.status.busy": "2024-04-27T13:59:35.819245Z", "iopub.status.idle": "2024-04-27T13:59:35.821165Z", "shell.execute_reply": "2024-04-27T13:59:35.820914Z" }, "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": "2024-04-27T13:59:35.822699Z", "iopub.status.busy": "2024-04-27T13:59:35.822578Z", "iopub.status.idle": "2024-04-27T13:59:35.824809Z", "shell.execute_reply": "2024-04-27T13:59:35.824490Z" }, "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": "2024-04-27T13:59:35.826748Z", "iopub.status.busy": "2024-04-27T13:59:35.826486Z", "iopub.status.idle": "2024-04-27T13:59:35.829184Z", "shell.execute_reply": "2024-04-27T13:59:35.828685Z" }, "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": "2024-04-27T13:59:35.831250Z", "iopub.status.busy": "2024-04-27T13:59:35.830910Z", "iopub.status.idle": "2024-04-27T13:59:35.833449Z", "shell.execute_reply": "2024-04-27T13:59:35.833202Z" }, "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": "2024-04-27T13:59:35.835431Z", "iopub.status.busy": "2024-04-27T13:59:35.835268Z", "iopub.status.idle": "2024-04-27T13:59:35.837951Z", "shell.execute_reply": "2024-04-27T13:59:35.837655Z" }, "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": "2024-04-27T13:59:35.839903Z", "iopub.status.busy": "2024-04-27T13:59:35.839771Z", "iopub.status.idle": "2024-04-27T13:59:35.842352Z", "shell.execute_reply": "2024-04-27T13:59:35.842052Z" }, "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": "2024-04-27T13:59:35.844029Z", "iopub.status.busy": "2024-04-27T13:59:35.843891Z", "iopub.status.idle": "2024-04-27T13:59:35.845658Z", "shell.execute_reply": "2024-04-27T13:59:35.845318Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from inspect import signature" ] }, { "cell_type": "code", "execution_count": 60, "metadata": { "execution": { "iopub.execute_input": "2024-04-27T13:59:35.847301Z", "iopub.status.busy": "2024-04-27T13:59:35.847180Z", "iopub.status.idle": "2024-04-27T13:59:35.848979Z", "shell.execute_reply": "2024-04-27T13:59:35.848581Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import warnings" ] }, { "cell_type": "code", "execution_count": 61, "metadata": { "execution": { "iopub.execute_input": "2024-04-27T13:59:35.850770Z", "iopub.status.busy": "2024-04-27T13:59:35.850602Z", "iopub.status.idle": "2024-04-27T13:59:35.852703Z", "shell.execute_reply": "2024-04-27T13:59:35.852341Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import os" ] }, { "cell_type": "code", "execution_count": 62, "metadata": { "execution": { "iopub.execute_input": "2024-04-27T13:59:35.854724Z", "iopub.status.busy": "2024-04-27T13:59:35.854548Z", "iopub.status.idle": "2024-04-27T13:59:35.883246Z", "shell.execute_reply": "2024-04-27T13:59:35.882773Z" }, "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": "2024-04-27T13:59:35.885347Z", "iopub.status.busy": "2024-04-27T13:59:35.885198Z", "iopub.status.idle": "2024-04-27T13:59:36.480096Z", "shell.execute_reply": "2024-04-27T13:59:36.479693Z" }, "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": "2024-04-27T13:59:36.481838Z", "iopub.status.busy": "2024-04-27T13:59:36.481695Z", "iopub.status.idle": "2024-04-27T13:59:36.938332Z", "shell.execute_reply": "2024-04-27T13:59:36.937740Z" }, "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": "2024-04-27T13:59:36.940524Z", "iopub.status.busy": "2024-04-27T13:59:36.940357Z", "iopub.status.idle": "2024-04-27T13:59:37.412535Z", "shell.execute_reply": "2024-04-27T13:59:37.411953Z" }, "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": "2024-04-27T13:59:37.414869Z", "iopub.status.busy": "2024-04-27T13:59:37.414711Z", "iopub.status.idle": "2024-04-27T13:59:37.898362Z", "shell.execute_reply": "2024-04-27T13:59:37.897870Z" }, "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 }