{ "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": "2022-01-24T10:08:57.670574Z", "iopub.status.busy": "2022-01-24T10:08:57.669295Z", "iopub.status.idle": "2022-01-24T10:08:57.895129Z", "shell.execute_reply": "2022-01-24T10:08:57.895685Z" }, "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": "2022-01-24T10:08:57.901970Z", "iopub.status.busy": "2022-01-24T10:08:57.900982Z", "iopub.status.idle": "2022-01-24T10:08:57.904160Z", "shell.execute_reply": "2022-01-24T10:08:57.905289Z" }, "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": "2022-01-24T10:08:57.913090Z", "iopub.status.busy": "2022-01-24T10:08:57.911887Z", "iopub.status.idle": "2022-01-24T10:08:57.914289Z", "shell.execute_reply": "2022-01-24T10:08:57.915371Z" }, "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": "2022-01-24T10:08:57.925506Z", "iopub.status.busy": "2022-01-24T10:08:57.924353Z", "iopub.status.idle": "2022-01-24T10:08:57.926969Z", "shell.execute_reply": "2022-01-24T10:08:57.927889Z" }, "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": "2022-01-24T10:08:57.935452Z", "iopub.status.busy": "2022-01-24T10:08:57.934425Z", "iopub.status.idle": "2022-01-24T10:08:57.938084Z", "shell.execute_reply": "2022-01-24T10:08:57.937157Z" }, "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": "2022-01-24T10:08:57.950576Z", "iopub.status.busy": "2022-01-24T10:08:57.949379Z", "iopub.status.idle": "2022-01-24T10:08:57.952352Z", "shell.execute_reply": "2022-01-24T10:08:57.953217Z" }, "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": "2022-01-24T10:08:57.962183Z", "iopub.status.busy": "2022-01-24T10:08:57.960874Z", "iopub.status.idle": "2022-01-24T10:08:57.964154Z", "shell.execute_reply": "2022-01-24T10:08:57.964971Z" }, "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": "2022-01-24T10:08:57.973982Z", "iopub.status.busy": "2022-01-24T10:08:57.970943Z", "iopub.status.idle": "2022-01-24T10:08:57.975793Z", "shell.execute_reply": "2022-01-24T10:08:57.976559Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "SomeType = List[Optional[Union[str, int]]]" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2022-01-24T10:08:57.984166Z", "iopub.status.busy": "2022-01-24T10:08:57.982937Z", "iopub.status.idle": "2022-01-24T10:08:57.986466Z", "shell.execute_reply": "2022-01-24T10:08:57.987194Z" }, "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": "2022-01-24T10:08:57.995546Z", "iopub.status.busy": "2022-01-24T10:08:57.994591Z", "iopub.status.idle": "2022-01-24T10:08:57.997519Z", "shell.execute_reply": "2022-01-24T10:08:57.996662Z" }, "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": "2022-01-24T10:08:58.006579Z", "iopub.status.busy": "2022-01-24T10:08:58.004276Z", "iopub.status.idle": "2022-01-24T10:08:58.009539Z", "shell.execute_reply": "2022-01-24T10:08:58.010422Z" }, "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": "2022-01-24T10:08:58.026827Z", "iopub.status.busy": "2022-01-24T10:08:58.025930Z", "iopub.status.idle": "2022-01-24T10:08:58.034156Z", "shell.execute_reply": "2022-01-24T10:08:58.034942Z" }, "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": "2022-01-24T10:08:58.042890Z", "iopub.status.busy": "2022-01-24T10:08:58.041299Z", "iopub.status.idle": "2022-01-24T10:08:58.047379Z", "shell.execute_reply": "2022-01-24T10:08:58.048055Z" }, "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": "2022-01-24T10:08:58.059730Z", "iopub.status.busy": "2022-01-24T10:08:58.058059Z", "iopub.status.idle": "2022-01-24T10:08:58.062833Z", "shell.execute_reply": "2022-01-24T10:08:58.064616Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def class_tree(cls: Type, lowest: 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": "2022-01-24T10:08:58.074133Z", "iopub.status.busy": "2022-01-24T10:08:58.072814Z", "iopub.status.idle": "2022-01-24T10:08:58.077685Z", "shell.execute_reply": "2022-01-24T10:08:58.078542Z" }, "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": "2022-01-24T10:08:58.086193Z", "iopub.status.busy": "2022-01-24T10:08:58.084854Z", "iopub.status.idle": "2022-01-24T10:08:58.090668Z", "shell.execute_reply": "2022-01-24T10:08:58.091920Z" }, "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": "2022-01-24T10:08:58.099830Z", "iopub.status.busy": "2022-01-24T10:08:58.098580Z", "iopub.status.idle": "2022-01-24T10:08:58.102062Z", "shell.execute_reply": "2022-01-24T10:08:58.102858Z" }, "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": "2022-01-24T10:08:58.115577Z", "iopub.status.busy": "2022-01-24T10:08:58.114427Z", "iopub.status.idle": "2022-01-24T10:08:58.116788Z", "shell.execute_reply": "2022-01-24T10:08:58.117389Z" }, "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": "2022-01-24T10:08:58.125159Z", "iopub.status.busy": "2022-01-24T10:08:58.124237Z", "iopub.status.idle": "2022-01-24T10:08:58.129767Z", "shell.execute_reply": "2022-01-24T10:08:58.129010Z" }, "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": "2022-01-24T10:08:58.135976Z", "iopub.status.busy": "2022-01-24T10:08:58.134938Z", "iopub.status.idle": "2022-01-24T10:08:58.137543Z", "shell.execute_reply": "2022-01-24T10:08:58.138724Z" }, "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": "2022-01-24T10:08:58.145066Z", "iopub.status.busy": "2022-01-24T10:08:58.144109Z", "iopub.status.idle": "2022-01-24T10:08:58.146235Z", "shell.execute_reply": "2022-01-24T10:08:58.147243Z" }, "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": "2022-01-24T10:08:58.154172Z", "iopub.status.busy": "2022-01-24T10:08:58.152724Z", "iopub.status.idle": "2022-01-24T10:08:58.155803Z", "shell.execute_reply": "2022-01-24T10:08:58.156536Z" }, "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": "2022-01-24T10:08:58.162162Z", "iopub.status.busy": "2022-01-24T10:08:58.161048Z", "iopub.status.idle": "2022-01-24T10:08:58.163863Z", "shell.execute_reply": "2022-01-24T10:08:58.164582Z" }, "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": "2022-01-24T10:08:58.172057Z", "iopub.status.busy": "2022-01-24T10:08:58.170585Z", "iopub.status.idle": "2022-01-24T10:08:58.176236Z", "shell.execute_reply": "2022-01-24T10:08:58.177116Z" }, "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": "2022-01-24T10:08:58.183944Z", "iopub.status.busy": "2022-01-24T10:08:58.182645Z", "iopub.status.idle": "2022-01-24T10:08:58.185524Z", "shell.execute_reply": "2022-01-24T10:08:58.186329Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "A_Class.__doc__" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "execution": { "iopub.execute_input": "2022-01-24T10:08:58.193877Z", "iopub.status.busy": "2022-01-24T10:08:58.192764Z", "iopub.status.idle": "2022-01-24T10:08:58.197137Z", "shell.execute_reply": "2022-01-24T10:08:58.197754Z" }, "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": "2022-01-24T10:08:58.205353Z", "iopub.status.busy": "2022-01-24T10:08:58.203751Z", "iopub.status.idle": "2022-01-24T10:08:58.209570Z", "shell.execute_reply": "2022-01-24T10:08:58.210551Z" }, "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": "2022-01-24T10:08:58.217705Z", "iopub.status.busy": "2022-01-24T10:08:58.216588Z", "iopub.status.idle": "2022-01-24T10:08:58.220944Z", "shell.execute_reply": "2022-01-24T10:08:58.221624Z" }, "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": "2022-01-24T10:08:58.227633Z", "iopub.status.busy": "2022-01-24T10:08:58.226681Z", "iopub.status.idle": "2022-01-24T10:08:58.228892Z", "shell.execute_reply": "2022-01-24T10:08:58.229599Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "D_Class.foo.__doc__" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "execution": { "iopub.execute_input": "2022-01-24T10:08:58.235754Z", "iopub.status.busy": "2022-01-24T10:08:58.234662Z", "iopub.status.idle": "2022-01-24T10:08:58.240454Z", "shell.execute_reply": "2022-01-24T10:08:58.241265Z" }, "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": "2022-01-24T10:08:58.249155Z", "iopub.status.busy": "2022-01-24T10:08:58.247962Z", "iopub.status.idle": "2022-01-24T10:08:58.250508Z", "shell.execute_reply": "2022-01-24T10:08:58.251303Z" }, "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": "2022-01-24T10:08:58.258455Z", "iopub.status.busy": "2022-01-24T10:08:58.257556Z", "iopub.status.idle": "2022-01-24T10:08:58.263180Z", "shell.execute_reply": "2022-01-24T10:08:58.264903Z" }, "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": "2022-01-24T10:08:58.274521Z", "iopub.status.busy": "2022-01-24T10:08:58.272789Z", "iopub.status.idle": "2022-01-24T10:08:58.279917Z", "shell.execute_reply": "2022-01-24T10:08:58.280878Z" }, "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": "2022-01-24T10:08:58.289596Z", "iopub.status.busy": "2022-01-24T10:08:58.288310Z", "iopub.status.idle": "2022-01-24T10:08:58.291535Z", "shell.execute_reply": "2022-01-24T10:08:58.292057Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def unknown() -> None:\n", " pass" ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "execution": { "iopub.execute_input": "2022-01-24T10:08:58.298415Z", "iopub.status.busy": "2022-01-24T10:08:58.297242Z", "iopub.status.idle": "2022-01-24T10:08:58.303717Z", "shell.execute_reply": "2022-01-24T10:08:58.305051Z" }, "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": "2022-01-24T10:08:58.312119Z", "iopub.status.busy": "2022-01-24T10:08:58.311286Z", "iopub.status.idle": "2022-01-24T10:08:58.313696Z", "shell.execute_reply": "2022-01-24T10:08:58.314516Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import html" ] }, { "cell_type": "code", "execution_count": 37, "metadata": { "execution": { "iopub.execute_input": "2022-01-24T10:08:58.323892Z", "iopub.status.busy": "2022-01-24T10:08:58.321541Z", "iopub.status.idle": "2022-01-24T10:08:58.325913Z", "shell.execute_reply": "2022-01-24T10:08:58.326537Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import re" ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "execution": { "iopub.execute_input": "2022-01-24T10:08:58.333903Z", "iopub.status.busy": "2022-01-24T10:08:58.332747Z", "iopub.status.idle": "2022-01-24T10:08:58.337269Z", "shell.execute_reply": "2022-01-24T10:08:58.335521Z" }, "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": "2022-01-24T10:08:58.348009Z", "iopub.status.busy": "2022-01-24T10:08:58.346407Z", "iopub.status.idle": "2022-01-24T10:08:58.352110Z", "shell.execute_reply": "2022-01-24T10:08:58.353092Z" }, "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": "2022-01-24T10:08:58.361035Z", "iopub.status.busy": "2022-01-24T10:08:58.359870Z", "iopub.status.idle": "2022-01-24T10:08:58.362847Z", "shell.execute_reply": "2022-01-24T10:08:58.364774Z" }, "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": "2022-01-24T10:08:58.374299Z", "iopub.status.busy": "2022-01-24T10:08:58.373132Z", "iopub.status.idle": "2022-01-24T10:08:58.378247Z", "shell.execute_reply": "2022-01-24T10:08:58.378859Z" }, "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": "2022-01-24T10:08:58.395622Z", "iopub.status.busy": "2022-01-24T10:08:58.394609Z", "iopub.status.idle": "2022-01-24T10:08:58.399971Z", "shell.execute_reply": "2022-01-24T10:08:58.401090Z" }, "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": "2022-01-24T10:08:58.417934Z", "iopub.status.busy": "2022-01-24T10:08:58.415116Z", "iopub.status.idle": "2022-01-24T10:08:58.420416Z", "shell.execute_reply": "2022-01-24T10:08:58.422076Z" }, "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": "2022-01-24T10:08:58.429840Z", "iopub.status.busy": "2022-01-24T10:08:58.428285Z", "iopub.status.idle": "2022-01-24T10:08:58.431657Z", "shell.execute_reply": "2022-01-24T10:08:58.432816Z" }, "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": "2022-01-24T10:08:58.444229Z", "iopub.status.busy": "2022-01-24T10:08:58.442518Z", "iopub.status.idle": "2022-01-24T10:08:58.447872Z", "shell.execute_reply": "2022-01-24T10:08:58.450184Z" }, "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": "2022-01-24T10:08:58.457865Z", "iopub.status.busy": "2022-01-24T10:08:58.456722Z", "iopub.status.idle": "2022-01-24T10:08:58.460041Z", "shell.execute_reply": "2022-01-24T10:08:58.460750Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "assert not defined_in('VAR', A_Class)" ] }, { "cell_type": "code", "execution_count": 47, "metadata": { "execution": { "iopub.execute_input": "2022-01-24T10:08:58.466699Z", "iopub.status.busy": "2022-01-24T10:08:58.465860Z", "iopub.status.idle": "2022-01-24T10:08:58.468317Z", "shell.execute_reply": "2022-01-24T10:08:58.468878Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "assert defined_in('VAR', B_Class)" ] }, { "cell_type": "code", "execution_count": 48, "metadata": { "execution": { "iopub.execute_input": "2022-01-24T10:08:58.475466Z", "iopub.status.busy": "2022-01-24T10:08:58.474269Z", "iopub.status.idle": "2022-01-24T10:08:58.477585Z", "shell.execute_reply": "2022-01-24T10:08:58.478523Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "assert not defined_in('VAR', C_Class)" ] }, { "cell_type": "code", "execution_count": 49, "metadata": { "execution": { "iopub.execute_input": "2022-01-24T10:08:58.490639Z", "iopub.status.busy": "2022-01-24T10:08:58.489081Z", "iopub.status.idle": "2022-01-24T10:08:58.493016Z", "shell.execute_reply": "2022-01-24T10:08:58.494213Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "assert not defined_in('VAR', D_Class)" ] }, { "cell_type": "code", "execution_count": 50, "metadata": { "execution": { "iopub.execute_input": "2022-01-24T10:08:58.512251Z", "iopub.status.busy": "2022-01-24T10:08:58.510767Z", "iopub.status.idle": "2022-01-24T10:08:58.515661Z", "shell.execute_reply": "2022-01-24T10:08:58.516790Z" }, "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": "2022-01-24T10:08:58.528011Z", "iopub.status.busy": "2022-01-24T10:08:58.526870Z", "iopub.status.idle": "2022-01-24T10:08:58.532247Z", "shell.execute_reply": "2022-01-24T10:08:58.533502Z" }, "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": "2022-01-24T10:08:58.542540Z", "iopub.status.busy": "2022-01-24T10:08:58.540905Z", "iopub.status.idle": "2022-01-24T10:08:58.546772Z", "shell.execute_reply": "2022-01-24T10:08:58.547513Z" }, "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": "2022-01-24T10:08:58.556413Z", "iopub.status.busy": "2022-01-24T10:08:58.554654Z", "iopub.status.idle": "2022-01-24T10:08:58.558097Z", "shell.execute_reply": "2022-01-24T10:08:58.558746Z" }, "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": "2022-01-24T10:08:58.567701Z", "iopub.status.busy": "2022-01-24T10:08:58.566034Z", "iopub.status.idle": "2022-01-24T10:08:58.572108Z", "shell.execute_reply": "2022-01-24T10:08:58.573210Z" }, "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": "2022-01-24T10:08:58.582087Z", "iopub.status.busy": "2022-01-24T10:08:58.580333Z", "iopub.status.idle": "2022-01-24T10:08:58.588503Z", "shell.execute_reply": "2022-01-24T10:08:58.589490Z" }, "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": "2022-01-24T10:08:58.598070Z", "iopub.status.busy": "2022-01-24T10:08:58.596416Z", "iopub.status.idle": "2022-01-24T10:08:58.606290Z", "shell.execute_reply": "2022-01-24T10:08:58.606913Z" }, "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": "2022-01-24T10:08:58.619920Z", "iopub.status.busy": "2022-01-24T10:08:58.618292Z", "iopub.status.idle": "2022-01-24T10:08:58.622511Z", "shell.execute_reply": "2022-01-24T10:08:58.623359Z" }, "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": "2022-01-24T10:08:58.630779Z", "iopub.status.busy": "2022-01-24T10:08:58.629538Z", "iopub.status.idle": "2022-01-24T10:08:58.635197Z", "shell.execute_reply": "2022-01-24T10:08:58.636634Z" }, "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": "2022-01-24T10:08:58.644794Z", "iopub.status.busy": "2022-01-24T10:08:58.643094Z", "iopub.status.idle": "2022-01-24T10:08:58.646545Z", "shell.execute_reply": "2022-01-24T10:08:58.647861Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from inspect import signature" ] }, { "cell_type": "code", "execution_count": 60, "metadata": { "execution": { "iopub.execute_input": "2022-01-24T10:08:58.655415Z", "iopub.status.busy": "2022-01-24T10:08:58.654449Z", "iopub.status.idle": "2022-01-24T10:08:58.657072Z", "shell.execute_reply": "2022-01-24T10:08:58.657757Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import warnings" ] }, { "cell_type": "code", "execution_count": 61, "metadata": { "execution": { "iopub.execute_input": "2022-01-24T10:08:58.664083Z", "iopub.status.busy": "2022-01-24T10:08:58.662805Z", "iopub.status.idle": "2022-01-24T10:08:58.668846Z", "shell.execute_reply": "2022-01-24T10:08:58.667290Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import os" ] }, { "cell_type": "code", "execution_count": 62, "metadata": { "execution": { "iopub.execute_input": "2022-01-24T10:08:58.729636Z", "iopub.status.busy": "2022-01-24T10:08:58.697362Z", "iopub.status.idle": "2022-01-24T10:08:58.733819Z", "shell.execute_reply": "2022-01-24T10:08:58.735042Z" }, "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": "2022-01-24T10:08:58.744761Z", "iopub.status.busy": "2022-01-24T10:08:58.742760Z", "iopub.status.idle": "2022-01-24T10:09:00.217774Z", "shell.execute_reply": "2022-01-24T10:09:00.218567Z" }, "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": "2022-01-24T10:09:00.233116Z", "iopub.status.busy": "2022-01-24T10:09:00.232316Z", "iopub.status.idle": "2022-01-24T10:09:01.657572Z", "shell.execute_reply": "2022-01-24T10:09:01.658519Z" }, "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": "2022-01-24T10:09:01.674025Z", "iopub.status.busy": "2022-01-24T10:09:01.668703Z", "iopub.status.idle": "2022-01-24T10:09:02.929975Z", "shell.execute_reply": "2022-01-24T10:09:02.930980Z" }, "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": "2022-01-24T10:09:02.951394Z", "iopub.status.busy": "2022-01-24T10:09:02.948317Z", "iopub.status.idle": "2022-01-24T10:09:04.169149Z", "shell.execute_reply": "2022-01-24T10:09:04.169717Z" }, "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.9.7" }, "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 }