{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#default_exp foundation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "from fastcore.imports import *\n", "from fastcore.basics import *\n", "from functools import lru_cache\n", "from contextlib import contextmanager\n", "from copy import copy\n", "from configparser import ConfigParser\n", "import random,pickle,inspect" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from fastcore.test import *\n", "from nbdev.showdoc import *\n", "from fastcore.nb_imports import *" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Foundation\n", "\n", "> The `L` class and helpers for it" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Foundational Functions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "@contextmanager\n", "def working_directory(path):\n", " \"Change working directory to `path` and return to previous on exit.\"\n", " prev_cwd = Path.cwd()\n", " os.chdir(path)\n", " try: yield\n", " finally: os.chdir(prev_cwd)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def add_docs(cls, cls_doc=None, **docs):\n", " \"Copy values from `docs` to `cls` docstrings, and confirm all public methods are documented\"\n", " if cls_doc is not None: cls.__doc__ = cls_doc\n", " for k,v in docs.items():\n", " f = getattr(cls,k)\n", " if hasattr(f,'__func__'): f = f.__func__ # required for class methods\n", " f.__doc__ = v\n", " # List of public callables without docstring\n", " nodoc = [c for n,c in vars(cls).items() if callable(c)\n", " and not n.startswith('_') and c.__doc__ is None]\n", " assert not nodoc, f\"Missing docs: {nodoc}\"\n", " assert cls.__doc__ is not None, f\"Missing class docs: {cls}\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`add_docs` allows you to add docstrings to a class and its associated methods. This function allows you to group docstrings together seperate from your code, which enables you to define one-line functions as well as organize your code more succintly. We believe this confers a number of benefits which we discuss in [our style guide](https://docs.fast.ai/dev/style.html).\n", "\n", "Suppose you have the following undocumented class:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class T:\n", " def foo(self): pass\n", " def bar(self): pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can add documentation to this class like so:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "add_docs(T, cls_doc=\"A docstring for the class.\",\n", " foo=\"The foo method.\",\n", " bar=\"The bar method.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, docstrings will appear as expected:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_eq(T.__doc__, \"A docstring for the class.\")\n", "test_eq(T.foo.__doc__, \"The foo method.\")\n", "test_eq(T.bar.__doc__, \"The bar method.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`add_docs` also validates that all of your public methods contain a docstring. If one of your methods is not documented, it will raise an error:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class T:\n", " def foo(self): pass\n", " def bar(self): pass\n", "\n", "f=lambda: add_docs(T, \"A docstring for the class.\", foo=\"The foo method.\")\n", "test_fail(f, contains=\"Missing docs\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "class _T:\n", " def f(self): pass\n", " @classmethod\n", " def g(cls): pass\n", "add_docs(_T, \"a\", f=\"f\", g=\"g\")\n", "\n", "test_eq(_T.__doc__, \"a\")\n", "test_eq(_T.f.__doc__, \"f\")\n", "test_eq(_T.g.__doc__, \"g\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def docs(cls):\n", " \"Decorator version of `add_docs`, using `_docs` dict\"\n", " add_docs(cls, **cls._docs)\n", " return cls" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Instead of using `add_docs`, you can use the decorator `docs` as shown below. Note that the docstring for the class can be set with the argument `cls_doc`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@docs\n", "class _T:\n", " def f(self): pass\n", " def g(cls): pass\n", " \n", " _docs = dict(cls_doc=\"The class docstring\", \n", " f=\"The docstring for method f.\",\n", " g=\"A different docstring for method g.\")\n", "\n", " \n", "test_eq(_T.__doc__, \"The class docstring\")\n", "test_eq(_T.f.__doc__, \"The docstring for method f.\")\n", "test_eq(_T.g.__doc__, \"A different docstring for method g.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For either the `docs` decorator or the `add_docs` function, you can still define your docstrings in the normal way. Below we set the docstring for the class as usual, but define the method docstrings through the `_docs` attribute:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@docs\n", "class _T:\n", " \"The class docstring\"\n", " def f(self): pass\n", " _docs = dict(f=\"The docstring for method f.\")\n", "\n", " \n", "test_eq(_T.__doc__, \"The class docstring\")\n", "test_eq(_T.f.__doc__, \"The docstring for method f.\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "
is_iter
[source]is_iter
(**`o`**)\n",
"\n",
"Test whether `o` can be used in a `for` loop"
],
"text/plain": [
"L.__getitem__
[source]L.__getitem__
(**`idx`**)\n",
"\n",
"Retrieve `idx` (can be list of indices, or mask, or int) items"
],
"text/plain": [
"L.__setitem__
[source]L.__setitem__
(**`idx`**, **`o`**)\n",
"\n",
"Set `idx` (can be list of indices, or mask, or int) items to `o` (which is broadcast if not iterable)"
],
"text/plain": [
"L.unique
[source]L.unique
(**`sort`**=*`False`*, **`bidir`**=*`False`*, **`start`**=*`None`*)\n",
"\n",
"Unique items, in stable order"
],
"text/plain": [
"L.val2idx
[source]L.val2idx
()\n",
"\n",
"Dict from value to index"
],
"text/plain": [
"L.filter
[source]L.filter
(**`f`**=*`noop`*, **`negate`**=*`False`*, **`gen`**=*`False`*, **\\*\\*`kwargs`**)\n",
"\n",
"Create new [`L`](/foundation.html#L) filtered by predicate `f`, passing `args` and `kwargs` to `f`"
],
"text/plain": [
"L.argwhere
[source]L.argwhere
(**`f`**, **`negate`**=*`False`*, **\\*\\*`kwargs`**)\n",
"\n",
"Like `filter`, but return indices for matching items"
],
"text/plain": [
"L.map
[source]L.map
(**`f`**, **\\*`args`**, **`gen`**=*`False`*, **\\*\\*`kwargs`**)\n",
"\n",
"Create new [`L`](/foundation.html#L) with `f` applied to all `items`, passing `args` and `kwargs` to `f`"
],
"text/plain": [
"L.map_dict
[source]L.map_dict
(**`f`**=*`noop`*, **\\*`args`**, **`gen`**=*`False`*, **\\*\\*`kwargs`**)\n",
"\n",
"Like `map`, but creates a dict from `items` to function results"
],
"text/plain": [
"L.zip
[source]L.zip
(**`cycled`**=*`False`*)\n",
"\n",
"Create new [`L`](/foundation.html#L) with `zip(*items)`"
],
"text/plain": [
"L.map_zip
[source]L.map_zip
(**`f`**, **\\*`args`**, **`cycled`**=*`False`*, **\\*\\*`kwargs`**)\n",
"\n",
"Combine `zip` and `starmap`"
],
"text/plain": [
"L.zipwith
[source]L.zipwith
(**\\*`rest`**, **`cycled`**=*`False`*)\n",
"\n",
"Create new [`L`](/foundation.html#L) with `self` zip with each of `*rest`"
],
"text/plain": [
"L.map_zipwith
[source]L.map_zipwith
(**`f`**, **\\*`rest`**, **`cycled`**=*`False`*, **\\*\\*`kwargs`**)\n",
"\n",
"Combine `zipwith` and `starmap`"
],
"text/plain": [
"L.itemgot
[source]L.itemgot
(**\\*`idxs`**)\n",
"\n",
"Create new [`L`](/foundation.html#L) with item `idx` of all `items`"
],
"text/plain": [
"L.attrgot
[source]L.attrgot
(**`k`**, **`default`**=*`None`*)\n",
"\n",
"Create new [`L`](/foundation.html#L) with attr `k` (or value `k` for dicts) of all `items`."
],
"text/plain": [
"L.sorted
[source]L.sorted
(**`key`**=*`None`*, **`reverse`**=*`False`*)\n",
"\n",
"New [`L`](/foundation.html#L) sorted by `key`. If key is str use `attrgetter`; if int use `itemgetter`"
],
"text/plain": [
"L.split
[source]L.split
(**`s`**, **`sep`**=*`None`*, **`maxsplit`**=*`-1`*)\n",
"\n",
"Class Method: Same as `str.split`, but returns an [`L`](/foundation.html#L)"
],
"text/plain": [
"L.range
[source]L.range
(**`a`**, **`b`**=*`None`*, **`step`**=*`None`*)\n",
"\n",
"Class Method: Same as `range`, but returns [`L`](/foundation.html#L). Can pass collection for `a`, to use `len(a)`"
],
"text/plain": [
"L.concat
[source]L.concat
()\n",
"\n",
"Concatenate all elements of list"
],
"text/plain": [
"L.copy
[source]L.copy
()\n",
"\n",
"Same as `list.copy`, but returns an [`L`](/foundation.html#L)"
],
"text/plain": [
"L.map_first
[source]L.map_first
(**`f`**=*`noop`*, **`g`**=*`noop`*, **\\*`args`**, **\\*\\*`kwargs`**)\n",
"\n",
"First element of `map_filter`"
],
"text/plain": [
"L.setattrs
[source]L.setattrs
(**`attr`**, **`val`**)\n",
"\n",
"Call `setattr` on all items"
],
"text/plain": [
"