{ "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-01-07T14:25:34.676691Z", "iopub.status.busy": "2023-01-07T14:25:34.676261Z", "iopub.status.idle": "2023-01-07T14:25:34.708532Z", "shell.execute_reply": "2023-01-07T14:25:34.708751Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import bookutils" ] }, { "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": "2023-01-07T14:25:34.710697Z", "iopub.status.busy": "2023-01-07T14:25:34.710375Z", "iopub.status.idle": "2023-01-07T14:25:34.711585Z", "shell.execute_reply": "2023-01-07T14:25:34.711785Z" }, "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-01-07T14:25:34.713586Z", "iopub.status.busy": "2023-01-07T14:25:34.713233Z", "iopub.status.idle": "2023-01-07T14:25:34.715387Z", "shell.execute_reply": "2023-01-07T14:25:34.715646Z" }, "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-01-07T14:25:34.719033Z", "iopub.status.busy": "2023-01-07T14:25:34.718659Z", "iopub.status.idle": "2023-01-07T14:25:34.719943Z", "shell.execute_reply": "2023-01-07T14:25:34.720267Z" }, "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-01-07T14:25:34.722434Z", "iopub.status.busy": "2023-01-07T14:25:34.722154Z", "iopub.status.idle": "2023-01-07T14:25:34.723239Z", "shell.execute_reply": "2023-01-07T14:25:34.723544Z" }, "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-01-07T14:25:34.725394Z", "iopub.status.busy": "2023-01-07T14:25:34.725081Z", "iopub.status.idle": "2023-01-07T14:25:34.726545Z", "shell.execute_reply": "2023-01-07T14:25:34.726302Z" }, "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-01-07T14:25:34.728599Z", "iopub.status.busy": "2023-01-07T14:25:34.728301Z", "iopub.status.idle": "2023-01-07T14:25:34.729604Z", "shell.execute_reply": "2023-01-07T14:25:34.729792Z" }, "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-01-07T14:25:34.731708Z", "iopub.status.busy": "2023-01-07T14:25:34.731243Z", "iopub.status.idle": "2023-01-07T14:25:34.732560Z", "shell.execute_reply": "2023-01-07T14:25:34.732962Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "SomeType = List[Optional[Union[str, int]]]" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2023-01-07T14:25:34.735123Z", "iopub.status.busy": "2023-01-07T14:25:34.734796Z", "iopub.status.idle": "2023-01-07T14:25:34.735936Z", "shell.execute_reply": "2023-01-07T14:25:34.736238Z" }, "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-01-07T14:25:34.738129Z", "iopub.status.busy": "2023-01-07T14:25:34.737834Z", "iopub.status.idle": "2023-01-07T14:25:34.738949Z", "shell.execute_reply": "2023-01-07T14:25:34.739149Z" }, "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-01-07T14:25:34.740781Z", "iopub.status.busy": "2023-01-07T14:25:34.740480Z", "iopub.status.idle": "2023-01-07T14:25:34.741825Z", "shell.execute_reply": "2023-01-07T14:25:34.742039Z" }, "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-01-07T14:25:34.745234Z", "iopub.status.busy": "2023-01-07T14:25:34.744836Z", "iopub.status.idle": "2023-01-07T14:25:34.765754Z", "shell.execute_reply": "2023-01-07T14:25:34.766087Z" }, "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-01-07T14:25:34.768673Z", "iopub.status.busy": "2023-01-07T14:25:34.768305Z", "iopub.status.idle": "2023-01-07T14:25:34.770168Z", "shell.execute_reply": "2023-01-07T14:25:34.770388Z" }, "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-01-07T14:25:34.773240Z", "iopub.status.busy": "2023-01-07T14:25:34.772937Z", "iopub.status.idle": "2023-01-07T14:25:34.774087Z", "shell.execute_reply": "2023-01-07T14:25:34.774416Z" }, "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-01-07T14:25:34.776822Z", "iopub.status.busy": "2023-01-07T14:25:34.776431Z", "iopub.status.idle": "2023-01-07T14:25:34.778209Z", "shell.execute_reply": "2023-01-07T14:25:34.778474Z" }, "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-01-07T14:25:34.780569Z", "iopub.status.busy": "2023-01-07T14:25:34.780242Z", "iopub.status.idle": "2023-01-07T14:25:34.782009Z", "shell.execute_reply": "2023-01-07T14:25:34.782265Z" }, "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-01-07T14:25:34.784165Z", "iopub.status.busy": "2023-01-07T14:25:34.783857Z", "iopub.status.idle": "2023-01-07T14:25:34.785082Z", "shell.execute_reply": "2023-01-07T14:25:34.785319Z" }, "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-01-07T14:25:34.788102Z", "iopub.status.busy": "2023-01-07T14:25:34.787736Z", "iopub.status.idle": "2023-01-07T14:25:34.788889Z", "shell.execute_reply": "2023-01-07T14:25:34.789207Z" }, "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-01-07T14:25:34.791344Z", "iopub.status.busy": "2023-01-07T14:25:34.790948Z", "iopub.status.idle": "2023-01-07T14:25:34.792606Z", "shell.execute_reply": "2023-01-07T14:25:34.792850Z" }, "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-01-07T14:25:34.794958Z", "iopub.status.busy": "2023-01-07T14:25:34.794591Z", "iopub.status.idle": "2023-01-07T14:25:34.795834Z", "shell.execute_reply": "2023-01-07T14:25:34.796076Z" }, "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-01-07T14:25:34.797902Z", "iopub.status.busy": "2023-01-07T14:25:34.797160Z", "iopub.status.idle": "2023-01-07T14:25:34.799352Z", "shell.execute_reply": "2023-01-07T14:25:34.799619Z" }, "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-01-07T14:25:34.801644Z", "iopub.status.busy": "2023-01-07T14:25:34.801258Z", "iopub.status.idle": "2023-01-07T14:25:34.802522Z", "shell.execute_reply": "2023-01-07T14:25:34.802908Z" }, "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-01-07T14:25:34.805023Z", "iopub.status.busy": "2023-01-07T14:25:34.804711Z", "iopub.status.idle": "2023-01-07T14:25:34.806307Z", "shell.execute_reply": "2023-01-07T14:25:34.806840Z" }, "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-01-07T14:25:34.809875Z", "iopub.status.busy": "2023-01-07T14:25:34.809427Z", "iopub.status.idle": "2023-01-07T14:25:34.810967Z", "shell.execute_reply": "2023-01-07T14:25:34.811305Z" }, "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-01-07T14:25:34.813314Z", "iopub.status.busy": "2023-01-07T14:25:34.812954Z", "iopub.status.idle": "2023-01-07T14:25:34.814030Z", "shell.execute_reply": "2023-01-07T14:25:34.814418Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "A_Class.__doc__" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "execution": { "iopub.execute_input": "2023-01-07T14:25:34.816778Z", "iopub.status.busy": "2023-01-07T14:25:34.816394Z", "iopub.status.idle": "2023-01-07T14:25:34.817890Z", "shell.execute_reply": "2023-01-07T14:25:34.818438Z" }, "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-01-07T14:25:34.820828Z", "iopub.status.busy": "2023-01-07T14:25:34.820473Z", "iopub.status.idle": "2023-01-07T14:25:34.822069Z", "shell.execute_reply": "2023-01-07T14:25:34.822260Z" }, "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-01-07T14:25:34.824226Z", "iopub.status.busy": "2023-01-07T14:25:34.823897Z", "iopub.status.idle": "2023-01-07T14:25:34.825358Z", "shell.execute_reply": "2023-01-07T14:25:34.825664Z" }, "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-01-07T14:25:34.827907Z", "iopub.status.busy": "2023-01-07T14:25:34.827454Z", "iopub.status.idle": "2023-01-07T14:25:34.828928Z", "shell.execute_reply": "2023-01-07T14:25:34.829304Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "D_Class.foo.__doc__" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "execution": { "iopub.execute_input": "2023-01-07T14:25:34.832182Z", "iopub.status.busy": "2023-01-07T14:25:34.831778Z", "iopub.status.idle": "2023-01-07T14:25:34.833326Z", "shell.execute_reply": "2023-01-07T14:25:34.833594Z" }, "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-01-07T14:25:34.835582Z", "iopub.status.busy": "2023-01-07T14:25:34.835244Z", "iopub.status.idle": "2023-01-07T14:25:34.836344Z", "shell.execute_reply": "2023-01-07T14:25:34.836577Z" }, "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-01-07T14:25:34.837948Z", "iopub.status.busy": "2023-01-07T14:25:34.837638Z", "iopub.status.idle": "2023-01-07T14:25:34.839677Z", "shell.execute_reply": "2023-01-07T14:25:34.839880Z" }, "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-01-07T14:25:34.841863Z", "iopub.status.busy": "2023-01-07T14:25:34.841527Z", "iopub.status.idle": "2023-01-07T14:25:34.843042Z", "shell.execute_reply": "2023-01-07T14:25:34.843330Z" }, "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-01-07T14:25:34.845301Z", "iopub.status.busy": "2023-01-07T14:25:34.844900Z", "iopub.status.idle": "2023-01-07T14:25:34.846274Z", "shell.execute_reply": "2023-01-07T14:25:34.846649Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def unknown() -> None:\n", " pass" ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "execution": { "iopub.execute_input": "2023-01-07T14:25:34.848871Z", "iopub.status.busy": "2023-01-07T14:25:34.848565Z", "iopub.status.idle": "2023-01-07T14:25:34.850087Z", "shell.execute_reply": "2023-01-07T14:25:34.850312Z" }, "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-01-07T14:25:34.852360Z", "iopub.status.busy": "2023-01-07T14:25:34.851958Z", "iopub.status.idle": "2023-01-07T14:25:34.853310Z", "shell.execute_reply": "2023-01-07T14:25:34.853560Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import html" ] }, { "cell_type": "code", "execution_count": 37, "metadata": { "execution": { "iopub.execute_input": "2023-01-07T14:25:34.855419Z", "iopub.status.busy": "2023-01-07T14:25:34.855120Z", "iopub.status.idle": "2023-01-07T14:25:34.856204Z", "shell.execute_reply": "2023-01-07T14:25:34.856528Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import re" ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "execution": { "iopub.execute_input": "2023-01-07T14:25:34.858632Z", "iopub.status.busy": "2023-01-07T14:25:34.858337Z", "iopub.status.idle": "2023-01-07T14:25:34.859666Z", "shell.execute_reply": "2023-01-07T14:25:34.859872Z" }, "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-01-07T14:25:34.861715Z", "iopub.status.busy": "2023-01-07T14:25:34.861395Z", "iopub.status.idle": "2023-01-07T14:25:34.862843Z", "shell.execute_reply": "2023-01-07T14:25:34.863188Z" }, "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-01-07T14:25:34.865231Z", "iopub.status.busy": "2023-01-07T14:25:34.864931Z", "iopub.status.idle": "2023-01-07T14:25:34.866008Z", "shell.execute_reply": "2023-01-07T14:25:34.866312Z" }, "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-01-07T14:25:34.868164Z", "iopub.status.busy": "2023-01-07T14:25:34.867846Z", "iopub.status.idle": "2023-01-07T14:25:34.869359Z", "shell.execute_reply": "2023-01-07T14:25:34.869562Z" }, "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-01-07T14:25:34.872699Z", "iopub.status.busy": "2023-01-07T14:25:34.872389Z", "iopub.status.idle": "2023-01-07T14:25:34.874098Z", "shell.execute_reply": "2023-01-07T14:25:34.874361Z" }, "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-01-07T14:25:34.877034Z", "iopub.status.busy": "2023-01-07T14:25:34.876743Z", "iopub.status.idle": "2023-01-07T14:25:34.877989Z", "shell.execute_reply": "2023-01-07T14:25:34.878309Z" }, "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-01-07T14:25:34.880271Z", "iopub.status.busy": "2023-01-07T14:25:34.879949Z", "iopub.status.idle": "2023-01-07T14:25:34.881173Z", "shell.execute_reply": "2023-01-07T14:25:34.881609Z" }, "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-01-07T14:25:34.884845Z", "iopub.status.busy": "2023-01-07T14:25:34.884419Z", "iopub.status.idle": "2023-01-07T14:25:34.885786Z", "shell.execute_reply": "2023-01-07T14:25:34.886108Z" }, "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-01-07T14:25:34.887949Z", "iopub.status.busy": "2023-01-07T14:25:34.887644Z", "iopub.status.idle": "2023-01-07T14:25:34.888867Z", "shell.execute_reply": "2023-01-07T14:25:34.889180Z" }, "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-01-07T14:25:34.890790Z", "iopub.status.busy": "2023-01-07T14:25:34.890493Z", "iopub.status.idle": "2023-01-07T14:25:34.891784Z", "shell.execute_reply": "2023-01-07T14:25:34.891963Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "assert defined_in('VAR', B_Class)" ] }, { "cell_type": "code", "execution_count": 48, "metadata": { "execution": { "iopub.execute_input": "2023-01-07T14:25:34.893641Z", "iopub.status.busy": "2023-01-07T14:25:34.893316Z", "iopub.status.idle": "2023-01-07T14:25:34.894742Z", "shell.execute_reply": "2023-01-07T14:25:34.894931Z" }, "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-01-07T14:25:34.896761Z", "iopub.status.busy": "2023-01-07T14:25:34.896447Z", "iopub.status.idle": "2023-01-07T14:25:34.897834Z", "shell.execute_reply": "2023-01-07T14:25:34.898025Z" }, "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-01-07T14:25:34.900113Z", "iopub.status.busy": "2023-01-07T14:25:34.899825Z", "iopub.status.idle": "2023-01-07T14:25:34.901086Z", "shell.execute_reply": "2023-01-07T14:25:34.901280Z" }, "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-01-07T14:25:34.903428Z", "iopub.status.busy": "2023-01-07T14:25:34.903102Z", "iopub.status.idle": "2023-01-07T14:25:34.904665Z", "shell.execute_reply": "2023-01-07T14:25:34.904862Z" }, "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-01-07T14:25:34.906980Z", "iopub.status.busy": "2023-01-07T14:25:34.906456Z", "iopub.status.idle": "2023-01-07T14:25:34.908626Z", "shell.execute_reply": "2023-01-07T14:25:34.908932Z" }, "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-01-07T14:25:34.911387Z", "iopub.status.busy": "2023-01-07T14:25:34.911091Z", "iopub.status.idle": "2023-01-07T14:25:34.912626Z", "shell.execute_reply": "2023-01-07T14:25:34.912841Z" }, "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-01-07T14:25:34.914849Z", "iopub.status.busy": "2023-01-07T14:25:34.914038Z", "iopub.status.idle": "2023-01-07T14:25:34.916240Z", "shell.execute_reply": "2023-01-07T14:25:34.916444Z" }, "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-01-07T14:25:34.918587Z", "iopub.status.busy": "2023-01-07T14:25:34.918228Z", "iopub.status.idle": "2023-01-07T14:25:34.919813Z", "shell.execute_reply": "2023-01-07T14:25:34.920006Z" }, "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-01-07T14:25:34.922151Z", "iopub.status.busy": "2023-01-07T14:25:34.921800Z", "iopub.status.idle": "2023-01-07T14:25:34.923483Z", "shell.execute_reply": "2023-01-07T14:25:34.923749Z" }, "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-01-07T14:25:34.926547Z", "iopub.status.busy": "2023-01-07T14:25:34.926048Z", "iopub.status.idle": "2023-01-07T14:25:34.927677Z", "shell.execute_reply": "2023-01-07T14:25:34.927958Z" }, "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-01-07T14:25:34.930440Z", "iopub.status.busy": "2023-01-07T14:25:34.930087Z", "iopub.status.idle": "2023-01-07T14:25:34.931717Z", "shell.execute_reply": "2023-01-07T14:25:34.931914Z" }, "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-01-07T14:25:34.933816Z", "iopub.status.busy": "2023-01-07T14:25:34.933522Z", "iopub.status.idle": "2023-01-07T14:25:34.934703Z", "shell.execute_reply": "2023-01-07T14:25:34.935035Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from inspect import signature" ] }, { "cell_type": "code", "execution_count": 60, "metadata": { "execution": { "iopub.execute_input": "2023-01-07T14:25:34.936942Z", "iopub.status.busy": "2023-01-07T14:25:34.936476Z", "iopub.status.idle": "2023-01-07T14:25:34.937684Z", "shell.execute_reply": "2023-01-07T14:25:34.937874Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import warnings" ] }, { "cell_type": "code", "execution_count": 61, "metadata": { "execution": { "iopub.execute_input": "2023-01-07T14:25:34.939373Z", "iopub.status.busy": "2023-01-07T14:25:34.939072Z", "iopub.status.idle": "2023-01-07T14:25:34.940475Z", "shell.execute_reply": "2023-01-07T14:25:34.940660Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import os" ] }, { "cell_type": "code", "execution_count": 62, "metadata": { "execution": { "iopub.execute_input": "2023-01-07T14:25:34.969095Z", "iopub.status.busy": "2023-01-07T14:25:34.968626Z", "iopub.status.idle": "2023-01-07T14:25:34.969786Z", "shell.execute_reply": "2023-01-07T14:25:34.970101Z" }, "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-01-07T14:25:34.972396Z", "iopub.status.busy": "2023-01-07T14:25:34.971981Z", "iopub.status.idle": "2023-01-07T14:25:35.338343Z", "shell.execute_reply": "2023-01-07T14:25:35.338601Z" }, "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-01-07T14:25:35.344961Z", "iopub.status.busy": "2023-01-07T14:25:35.342603Z", "iopub.status.idle": "2023-01-07T14:25:35.628136Z", "shell.execute_reply": "2023-01-07T14:25:35.628613Z" }, "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-01-07T14:25:35.633185Z", "iopub.status.busy": "2023-01-07T14:25:35.632590Z", "iopub.status.idle": "2023-01-07T14:25:35.907277Z", "shell.execute_reply": "2023-01-07T14:25:35.907556Z" }, "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-01-07T14:25:35.913467Z", "iopub.status.busy": "2023-01-07T14:25:35.913120Z", "iopub.status.idle": "2023-01-07T14:25:36.197748Z", "shell.execute_reply": "2023-01-07T14:25:36.197979Z" }, "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 }