# Notebook Development Utilities


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

``` python
```

    The autoreload extension is already loaded. To reload it, use:
      %reload_ext autoreload

## Class Definitions

One challenge with a highly iterative notebook-driven development
workflow where we’re taking small steps is defining classes. Generally,
you’d define the entire class in one giant cell, which makes it hard to
add prose around it or break it down into small executable pieces.

To address this challenge, we’re going to add a couple helpers that are
inspired from the wonderful
[fastcore](https://github.com/AnswerDotAI/fastcore) library: `patch` and
`patch_to`. These will allow easily monkeypatching a class so we can
define bits of it at a time.

------------------------------------------------------------------------

### patch_to

``` python

def patch_to(
    o:object
)->Callable:

```

*Decorator that takes in a object and attaches the decorated function
onto it.*

<details open class="code-fold">
<summary>Exported source</summary>

``` python
def patch_to(o: object) -> Callable:
    "Decorator that takes in a object and attaches the decorated function onto it."
    def inner(fn: Callable) -> Callable:
        print(fn)
        nm = callable_name(fn)
        setattr(o, nm, fn)
        return fn
    return inner
```

</details>

Okay, let’s try it out. We’ll create a dummy class named `_T`.

``` python
class _T:
    pass
```

Now, we’ll attach on a function to `_T` in a separate code cell. This
allows us to, say, define the `__init__` with the class definition of
`_T` together and document those and then document each individual
function one by one separately with lots of examples.

``` python
@patch_to(_T)
def say_hello(self: _T, name: str) -> str:
    return f"Hello, {name}"
```

    <function say_hello>

So, now `_T` should have a `say_hello` function on it. Let’s confirm.

``` python
_t = _T()
```

``` python
assert _t.say_hello('eugene') == 'Hello, eugene'
```

Okay, so that works. But why are we getting `__name__` *or*
`__class__.__name__`? It turns out that classes in Python can be
callable if they have a `__call__` function defined, but instances of
those classes don’t have a `__name__`.

``` python
class _TC:
    def __call__(self, x):
        return x + x
```

``` python
_tc = _TC()
with test.raises(AttributeError):
    _tc.__name__
```

Now, that’s cool and all but notice that `self: _T` and `@patch_to(_T)`
are both two different ways to provide the same sort of information
(attach this method onto `_T`). So, now let’s take a look into adding a
`patch` decorator which looks at the type hint to determine which object
to patch.

We already have `patch_to`, so all we need to do inside `patch` is find
the object to patch and call `patch_to`. Now, how do we do that? Let’s
take a look at `inspect`.

``` python
inspect.get_annotations(patch_to)
```

    {'o': object, 'return': typing.Callable}

Okay, so if we assume the object we’re patching will always be called
`self` i.e. patching a class, then this should be easy-peasy.

------------------------------------------------------------------------

### patch

``` python

def patch(
    fn:Callable
)->Callable:

```

*Decorator to patch the object of the type-hinted ‘self’ argument of
`fn` with `fn`.*

Let’s see if we can try `patch` out now with our dummy `_T` class.

``` python
@patch
def say_goodbye(self: _T, name: str) -> str:
    return f'Goodbye, {name}'
```

    <function say_goodbye>

Cool, can our `_t` object now say goodbye?

``` python
test.equal(_t.say_goodbye('eugene'), 'Goodbye, eugene')
```

``` python
say_goodbye.__qualname__
```

    'say_goodbye'

``` python
class MyCallable:
       def __call__(self, x):
           return x
   
obj = MyCallable()
type(say_goodbye)
```

    function
