diff --git a/fastcore/_nbdev.py b/fastcore/_nbdev.py index 35a69024..5643adac 100644 --- a/fastcore/_nbdev.py +++ b/fastcore/_nbdev.py @@ -103,6 +103,10 @@ "instantiate": "01_basics.ipynb", "using_attr": "01_basics.ipynb", "Self": "01_basics.ipynb", + "copy_func": "01_basics.ipynb", + "patch_to": "01_basics.ipynb", + "patch": "01_basics.ipynb", + "patch_property": "01_basics.ipynb", "Stateful": "01_basics.ipynb", "PrettyString": "01_basics.ipynb", "even_mults": "01_basics.ipynb", @@ -110,10 +114,6 @@ "defaults.cpus": "01_basics.ipynb", "add_props": "01_basics.ipynb", "typed": "01_basics.ipynb", - "copy_func": "02_foundation.ipynb", - "patch_to": "02_foundation.ipynb", - "patch": "02_foundation.ipynb", - "patch_property": "02_foundation.ipynb", "working_directory": "02_foundation.ipynb", "add_docs": "02_foundation.ipynb", "docs": "02_foundation.ipynb", diff --git a/fastcore/basics.py b/fastcore/basics.py index 17129089..cc82f526 100644 --- a/fastcore/basics.py +++ b/fastcore/basics.py @@ -10,11 +10,12 @@ 'filter_keys', 'filter_values', 'cycle', 'zip_cycle', 'sorted_ex', 'negate_func', 'argwhere', 'filter_ex', 'range_of', 'renumerate', 'first', 'nested_attr', 'nested_idx', 'val2idx', 'uniqueify', 'num_methods', 'rnum_methods', 'inum_methods', 'fastuple', 'arg0', 'arg1', 'arg2', 'arg3', 'arg4', 'bind', 'map_ex', - 'compose', 'maps', 'partialler', 'instantiate', 'using_attr', 'Self', 'Self', 'Stateful', 'PrettyString', - 'even_mults', 'num_cpus', 'add_props', 'typed'] + 'compose', 'maps', 'partialler', 'instantiate', 'using_attr', 'Self', 'Self', 'copy_func', 'patch_to', + 'patch', 'patch_property', 'Stateful', 'PrettyString', 'even_mults', 'num_cpus', 'add_props', 'typed'] # Cell from .imports import * +import builtins # Cell defaults = SimpleNamespace() @@ -688,6 +689,48 @@ def __call__(self,*args,**kwargs): return self.__getattr__('_call')(*args,**kwar # Cell #nbdev_comment _all_ = ['Self'] +# Cell +def copy_func(f): + "Copy a non-builtin function (NB `copy.copy` does not work for this)" + if not isinstance(f,FunctionType): return copy(f) + fn = FunctionType(f.__code__, f.__globals__, f.__name__, f.__defaults__, f.__closure__) + fn.__kwdefaults__ = f.__kwdefaults__ + fn.__dict__.update(f.__dict__) + return fn + +# Cell +def patch_to(cls, as_prop=False, cls_method=False): + "Decorator: add `f` to `cls`" + if not isinstance(cls, (tuple,list)): cls=(cls,) + def _inner(f): + for c_ in cls: + nf = copy_func(f) + nm = f.__name__ + # `functools.update_wrapper` when passing patched function to `Pipeline`, so we do it manually + for o in functools.WRAPPER_ASSIGNMENTS: setattr(nf, o, getattr(f,o)) + nf.__qualname__ = f"{c_.__name__}.{nm}" + if cls_method: + setattr(c_, nm, MethodType(nf, c_)) + else: + setattr(c_, nm, property(nf) if as_prop else nf) + # Avoid clobbering existing functions + return globals().get(nm, builtins.__dict__.get(nm, None)) + return _inner + +# Cell +def patch(f=None, *, as_prop=False, cls_method=False): + "Decorator: add `f` to the first parameter's class (based on f's type annotations)" + if f is None: return partial(patch, as_prop=as_prop, cls_method=cls_method) + cls = next(iter(f.__annotations__.values())) + return patch_to(cls, as_prop=as_prop, cls_method=cls_method)(f) + +# Cell +def patch_property(f): + "Deprecated; use `patch(as_prop=True)` instead" + warnings.warn("`patch_property` is deprecated and will be removed; use `patch(as_prop=True)` instead") + cls = next(iter(f.__annotations__.values())) + return patch_to(cls, as_prop=True)(f) + # Cell class Stateful: "A base class/mixin for objects that should not serialize all their state" diff --git a/fastcore/foundation.py b/fastcore/foundation.py index eb5ae8c8..a99f60f6 100644 --- a/fastcore/foundation.py +++ b/fastcore/foundation.py @@ -1,8 +1,7 @@ # AUTOGENERATED! DO NOT EDIT! File to edit: nbs/02_foundation.ipynb (unless otherwise specified). -__all__ = ['copy_func', 'patch_to', 'patch', 'patch_property', 'working_directory', 'add_docs', 'docs', 'coll_repr', - 'is_bool', 'mask2idxs', 'cycle', 'zip_cycle', 'is_indexer', 'GetAttr', 'delegate_attr', 'CollBase', 'L', - 'save_config_file', 'read_config_file', 'Config'] +__all__ = ['working_directory', 'add_docs', 'docs', 'coll_repr', 'is_bool', 'mask2idxs', 'cycle', 'zip_cycle', + 'is_indexer', 'GetAttr', 'delegate_attr', 'CollBase', 'L', 'save_config_file', 'read_config_file', 'Config'] # Cell from .imports import * @@ -13,46 +12,6 @@ from configparser import ConfigParser import random,pickle -# Cell -def copy_func(f): - "Copy a non-builtin function (NB `copy.copy` does not work for this)" - if not isinstance(f,FunctionType): return copy(f) - fn = FunctionType(f.__code__, f.__globals__, f.__name__, f.__defaults__, f.__closure__) - fn.__kwdefaults__ = f.__kwdefaults__ - fn.__dict__.update(f.__dict__) - return fn - -# Cell -def patch_to(cls, as_prop=False, cls_method=False): - "Decorator: add `f` to `cls`" - if not isinstance(cls, (tuple,list)): cls=(cls,) - def _inner(f): - for c_ in cls: - nf = copy_func(f) - # `functools.update_wrapper` when passing patched function to `Pipeline`, so we do it manually - for o in functools.WRAPPER_ASSIGNMENTS: setattr(nf, o, getattr(f,o)) - nf.__qualname__ = f"{c_.__name__}.{f.__name__}" - if cls_method: - setattr(c_, f.__name__, MethodType(nf, c_)) - else: - setattr(c_, f.__name__, property(nf) if as_prop else nf) - return f - return _inner - -# Cell -def patch(f=None, *, as_prop=False, cls_method=False): - "Decorator: add `f` to the first parameter's class (based on f's type annotations)" - if f is None: return partial(patch, as_prop=as_prop, cls_method=cls_method) - cls = next(iter(f.__annotations__.values())) - return patch_to(cls, as_prop=as_prop, cls_method=cls_method)(f) - -# Cell -def patch_property(f): - "Deprecated; use `patch(as_prop=True)` instead" - warnings.warn("`patch_property` is deprecated and will be removed; use `patch(as_prop=True)` instead") - cls = next(iter(f.__annotations__.values())) - return patch_to(cls, as_prop=True)(f) - # Cell @contextmanager def working_directory(path): diff --git a/fastcore/xtras.py b/fastcore/xtras.py index 8b8fc7dd..15cdcedd 100644 --- a/fastcore/xtras.py +++ b/fastcore/xtras.py @@ -299,7 +299,7 @@ def _socket_det(port,host,dgram): return family,addr,(socket.SOCK_STREAM,socket.SOCK_DGRAM)[dgram] # Cell -def start_server(port, host=None, dgram=False, n_queue=None): +def start_server(port, host=None, dgram=False, reuse_addr=True, n_queue=None): "Create a `socket` server on `port`, with optional `host`, of type `dgram`" listen_args = [n_queue] if n_queue else [] family,addr,typ = _socket_det(port,host,dgram) @@ -307,6 +307,7 @@ def start_server(port, host=None, dgram=False, n_queue=None): if os.path.exists(addr): os.unlink(addr) assert not os.path.exists(addr), f"{addr} in use" s = socket.socket(family, typ) + if reuse_addr and family==socket.AF_INET: s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(addr) s.listen(*listen_args) return s diff --git a/nbs/01_basics.ipynb b/nbs/01_basics.ipynb index c06c82e7..4dea56e2 100644 --- a/nbs/01_basics.ipynb +++ b/nbs/01_basics.ipynb @@ -16,7 +16,8 @@ "outputs": [], "source": [ "#export\n", - "from fastcore.imports import *" + "from fastcore.imports import *\n", + "import builtins" ] }, { @@ -3681,6 +3682,330 @@ "list(map(fg, [f,g]))" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Patching" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#export\n", + "def copy_func(f):\n", + " \"Copy a non-builtin function (NB `copy.copy` does not work for this)\"\n", + " if not isinstance(f,FunctionType): return copy(f)\n", + " fn = FunctionType(f.__code__, f.__globals__, f.__name__, f.__defaults__, f.__closure__)\n", + " fn.__kwdefaults__ = f.__kwdefaults__\n", + " fn.__dict__.update(f.__dict__)\n", + " return fn" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Sometimes it may be desirable to make a copy of a function that doesn't point to the original object. When you use Python's built in `copy.copy` or `copy.deepcopy` to copy a function, you get a reference to the original object:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import copy as cp\n", + "def foo(): pass\n", + "a = cp.copy(foo)\n", + "b = cp.deepcopy(foo)\n", + "\n", + "a.someattr = 'hello' # since a and b point at the same object, updating a will update b\n", + "test_eq(b.someattr, 'hello')\n", + "\n", + "assert a is foo and b is foo" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, with `copy_func`, you can retrieve a copy of a function without a reference to the original object:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "c = copy_func(foo) # c is an indpendent object\n", + "assert c is not foo" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def g(x, *, y=3):\n", + " return x+y\n", + "test_eq(copy_func(g)(4), 7)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#export\n", + "def patch_to(cls, as_prop=False, cls_method=False):\n", + " \"Decorator: add `f` to `cls`\"\n", + " if not isinstance(cls, (tuple,list)): cls=(cls,)\n", + " def _inner(f):\n", + " for c_ in cls:\n", + " nf = copy_func(f)\n", + " nm = f.__name__\n", + " # `functools.update_wrapper` when passing patched function to `Pipeline`, so we do it manually\n", + " for o in functools.WRAPPER_ASSIGNMENTS: setattr(nf, o, getattr(f,o))\n", + " nf.__qualname__ = f\"{c_.__name__}.{nm}\"\n", + " if cls_method:\n", + " setattr(c_, nm, MethodType(nf, c_))\n", + " else:\n", + " setattr(c_, nm, property(nf) if as_prop else nf)\n", + " # Avoid clobbering existing functions\n", + " return globals().get(nm, builtins.__dict__.get(nm, None))\n", + " return _inner" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `@patch_to` decorator allows you to [monkey patch](https://stackoverflow.com/questions/5626193/what-is-monkey-patching) a function into a class as a method:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class _T3(int): pass \n", + "\n", + "@patch_to(_T3)\n", + "def func1(self, a): return self+a\n", + "\n", + "t = _T3(1) # we initilized `t` to a type int = 1\n", + "test_eq(t.func1(2), 3) # we add 2 to `t`, so 2 + 1 = 3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can access instance properties in the usual way via `self`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class _T4():\n", + " def __init__(self, g): self.g = g\n", + " \n", + "@patch_to(_T4)\n", + "def greet(self, x): return self.g + x\n", + " \n", + "t = _T4('hello ') # this sets self.g = 'helllo '\n", + "test_eq(t.greet('world'), 'hello world') #t.greet('world') will append 'world' to 'hello '" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can instead specify that the method should be a class method by setting `cls_method=True`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class _T5(int): attr = 3 # attr is a class attribute we will access in a later method\n", + " \n", + "@patch_to(_T5, cls_method=True)\n", + "def func(cls, x): return cls.attr + x # you can access class attributes in the normal way\n", + "\n", + "test_eq(_T5.func(4), 7)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Additionally you can specify that the function you want to patch should be a class attribute with `as_prop` = False" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "@patch_to(_T5, as_prop=True)\n", + "def add_ten(self): return self + 10\n", + "\n", + "t = _T5(4)\n", + "test_eq(t.add_ten, 14)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Instead of passing one class to the `@patch_to` decorator, you can pass multiple classes in a tuple to simulteanously patch more than one class with the same method:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class _T6(int): pass\n", + "class _T7(int): pass\n", + "\n", + "@patch_to((_T6,_T7))\n", + "def func_mult(self, a): return self*a\n", + "\n", + "t = _T6(2)\n", + "test_eq(t.func_mult(4), 8)\n", + "t = _T7(2)\n", + "test_eq(t.func_mult(4), 8)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#export\n", + "def patch(f=None, *, as_prop=False, cls_method=False):\n", + " \"Decorator: add `f` to the first parameter's class (based on f's type annotations)\"\n", + " if f is None: return partial(patch, as_prop=as_prop, cls_method=cls_method)\n", + " cls = next(iter(f.__annotations__.values()))\n", + " return patch_to(cls, as_prop=as_prop, cls_method=cls_method)(f)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`@patch` is an alternative to `@patch_to` that allows you similarly monkey patch class(es) by using [type annotations](https://docs.python.org/3/library/typing.html):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class _T8(int): pass \n", + "\n", + "@patch\n", + "def func(self:_T8, a): return self+a\n", + "\n", + "t = _T8(1) # we initilized `t` to a type int = 1\n", + "test_eq(t.func(3), 4) # we add 3 to `t`, so 3 + 1 = 4\n", + "test_eq(t.func.__qualname__, '_T8.func')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Similarly to `patch_to`, you can supply a tuple of classes instead of a single class in your type annotations to patch multiple classes:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class _T9(int): pass \n", + "\n", + "@patch\n", + "def func2(x:(_T8,_T9), a): return x*a # will patch both _T8 and _T9\n", + "\n", + "t = _T8(2)\n", + "test_eq(t.func2(4), 8)\n", + "test_eq(t.func2.__qualname__, '_T8.func2')\n", + "\n", + "t = _T9(2)\n", + "test_eq(t.func2(4), 8)\n", + "test_eq(t.func2.__qualname__, '_T9.func2')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Just like `patch_to` decorator you can use `as_propas_prop` and `cls_method` parameters with `patch` decorator:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "@patch(as_prop=True)\n", + "def add_ten(self:_T5): return self + 10\n", + "\n", + "t = _T5(4)\n", + "test_eq(t.add_ten, 14)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class _T5(int): attr = 3 # attr is a class attribute we will access in a later method\n", + " \n", + "@patch(cls_method=True)\n", + "def func(cls:_T5, x): return cls.attr + x # you can access class attributes in the normal way\n", + "\n", + "test_eq(_T5.func(4), 7)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#export\n", + "def patch_property(f):\n", + " \"Deprecated; use `patch(as_prop=True)` instead\"\n", + " warnings.warn(\"`patch_property` is deprecated and will be removed; use `patch(as_prop=True)` instead\")\n", + " cls = next(iter(f.__annotations__.values()))\n", + " return patch_to(cls, as_prop=True)(f)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -4278,7 +4603,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "These variables are availabe as booleans in `fastcore.basics` as `IN_IPYTHON`, `IN_JUPYTER`, `IN_COLAB` and `IN_NOTEBOOK`." + "These variables are available as booleans in `fastcore.basics` as `IN_IPYTHON`, `IN_JUPYTER`, `IN_COLAB` and `IN_NOTEBOOK`." ] }, { diff --git a/nbs/02_foundation.ipynb b/nbs/02_foundation.ipynb index 4fba6a74..de4473fa 100644 --- a/nbs/02_foundation.ipynb +++ b/nbs/02_foundation.ipynb @@ -53,321 +53,6 @@ "## Foundational Functions" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#export\n", - "def copy_func(f):\n", - " \"Copy a non-builtin function (NB `copy.copy` does not work for this)\"\n", - " if not isinstance(f,FunctionType): return copy(f)\n", - " fn = FunctionType(f.__code__, f.__globals__, f.__name__, f.__defaults__, f.__closure__)\n", - " fn.__kwdefaults__ = f.__kwdefaults__\n", - " fn.__dict__.update(f.__dict__)\n", - " return fn" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Sometimes it may be desirable to make a copy of a function that doesn't point to the original object. When you use Python's built in `copy.copy` or `copy.deepcopy` to copy a function, you get a reference to the original object:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import copy as cp\n", - "def foo(): pass\n", - "a = cp.copy(foo)\n", - "b = cp.deepcopy(foo)\n", - "\n", - "a.someattr = 'hello' # since a and b point at the same object, updating a will update b\n", - "test_eq(b.someattr, 'hello')\n", - "\n", - "assert a is foo and b is foo" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "However, with `copy_func`, you can retrieve a copy of a function without a reference to the original object:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "c = copy_func(foo) # c is an indpendent object\n", - "assert c is not foo" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def g(x, *, y=3):\n", - " return x+y\n", - "test_eq(copy_func(g)(4), 7)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#export\n", - "def patch_to(cls, as_prop=False, cls_method=False):\n", - " \"Decorator: add `f` to `cls`\"\n", - " if not isinstance(cls, (tuple,list)): cls=(cls,)\n", - " def _inner(f):\n", - " for c_ in cls:\n", - " nf = copy_func(f)\n", - " # `functools.update_wrapper` when passing patched function to `Pipeline`, so we do it manually\n", - " for o in functools.WRAPPER_ASSIGNMENTS: setattr(nf, o, getattr(f,o))\n", - " nf.__qualname__ = f\"{c_.__name__}.{f.__name__}\"\n", - " if cls_method:\n", - " setattr(c_, f.__name__, MethodType(nf, c_))\n", - " else:\n", - " setattr(c_, f.__name__, property(nf) if as_prop else nf)\n", - " return f\n", - " return _inner" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `@patch_to` decorator allows you to [monkey patch](https://stackoverflow.com/questions/5626193/what-is-monkey-patching) a function into a class as a method:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class _T3(int): pass \n", - "\n", - "@patch_to(_T3)\n", - "def func1(self, a): return self+a\n", - "\n", - "t = _T3(1) # we initilized `t` to a type int = 1\n", - "test_eq(t.func1(2), 3) # we add 2 to `t`, so 2 + 1 = 3" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can access instance properties in the usual way via `self`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class _T4():\n", - " def __init__(self, g): self.g = g\n", - " \n", - "@patch_to(_T4)\n", - "def greet(self, x): return self.g + x\n", - " \n", - "t = _T4('hello ') # this sets self.g = 'helllo '\n", - "test_eq(t.greet('world'), 'hello world') #t.greet('world') will append 'world' to 'hello '" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can instead specify that the method should be a class method by setting `cls_method=True`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class _T5(int): attr = 3 # attr is a class attribute we will access in a later method\n", - " \n", - "@patch_to(_T5, cls_method=True)\n", - "def func(cls, x): return cls.attr + x # you can access class attributes in the normal way\n", - "\n", - "test_eq(_T5.func(4), 7)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Additionally you can specify that the function you want to patch should be a class attribute with `as_prop` = False" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "@patch_to(_T5, as_prop=True)\n", - "def add_ten(self): return self + 10\n", - "\n", - "t = _T5(4)\n", - "test_eq(t.add_ten, 14)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Instead of passing one class to the `@patch_to` decorator, you can pass multiple classes in a tuple to simulteanously patch more than one class with the same method:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class _T6(int): pass\n", - "class _T7(int): pass\n", - "\n", - "@patch_to((_T6,_T7))\n", - "def func_mult(self, a): return self*a\n", - "\n", - "t = _T6(2)\n", - "test_eq(t.func_mult(4), 8)\n", - "t = _T7(2)\n", - "test_eq(t.func_mult(4), 8)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#export\n", - "def patch(f=None, *, as_prop=False, cls_method=False):\n", - " \"Decorator: add `f` to the first parameter's class (based on f's type annotations)\"\n", - " if f is None: return partial(patch, as_prop=as_prop, cls_method=cls_method)\n", - " cls = next(iter(f.__annotations__.values()))\n", - " return patch_to(cls, as_prop=as_prop, cls_method=cls_method)(f)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`@patch` is an alternative to `@patch_to` that allows you similarly monkey patch class(es) by using [type annotations](https://docs.python.org/3/library/typing.html):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class _T8(int): pass \n", - "\n", - "@patch\n", - "def func(self:_T8, a): return self+a\n", - "\n", - "t = _T8(1) # we initilized `t` to a type int = 1\n", - "test_eq(t.func(3), 4) # we add 3 to `t`, so 3 + 1 = 4\n", - "test_eq(t.func.__qualname__, '_T8.func')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Similarly to `patch_to`, you can supply a tuple of classes instead of a single class in your type annotations to patch multiple classes:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class _T9(int): pass \n", - "\n", - "@patch\n", - "def func2(x:(_T8,_T9), a): return x*a # will patch both _T8 and _T9\n", - "\n", - "t = _T8(2)\n", - "test_eq(t.func2(4), 8)\n", - "test_eq(t.func2.__qualname__, '_T8.func2')\n", - "\n", - "t = _T9(2)\n", - "test_eq(t.func2(4), 8)\n", - "test_eq(t.func2.__qualname__, '_T9.func2')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Just like `patch_to` decorator you can use `as_propas_prop` and `cls_method` parameters with `patch` decorator:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "@patch(as_prop=True)\n", - "def add_ten(self:_T5): return self + 10\n", - "\n", - "t = _T5(4)\n", - "test_eq(t.add_ten, 14)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class _T5(int): attr = 3 # attr is a class attribute we will access in a later method\n", - " \n", - "@patch(cls_method=True)\n", - "def func(cls:_T5, x): return cls.attr + x # you can access class attributes in the normal way\n", - "\n", - "test_eq(_T5.func(4), 7)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#export\n", - "def patch_property(f):\n", - " \"Deprecated; use `patch(as_prop=True)` instead\"\n", - " warnings.warn(\"`patch_property` is deprecated and will be removed; use `patch(as_prop=True)` instead\")\n", - " cls = next(iter(f.__annotations__.values()))\n", - " return patch_to(cls, as_prop=True)(f)" - ] - }, { "cell_type": "code", "execution_count": null, diff --git a/nbs/03_xtras.ipynb b/nbs/03_xtras.ipynb index d493cee6..232d7053 100644 --- a/nbs/03_xtras.ipynb +++ b/nbs/03_xtras.ipynb @@ -588,7 +588,7 @@ { "data": { "text/plain": [ - "['e', 'g', 'a', 'd', 'c', 'b', 'f', 'h']" + "['h', 'e', 'f', 'b', 'c', 'a', 'g', 'd']" ] }, "execution_count": null, @@ -1513,7 +1513,7 @@ "outputs": [], "source": [ "#export\n", - "def start_server(port, host=None, dgram=False, n_queue=None):\n", + "def start_server(port, host=None, dgram=False, reuse_addr=True, n_queue=None):\n", " \"Create a `socket` server on `port`, with optional `host`, of type `dgram`\"\n", " listen_args = [n_queue] if n_queue else []\n", " family,addr,typ = _socket_det(port,host,dgram)\n", @@ -1521,6 +1521,7 @@ " if os.path.exists(addr): os.unlink(addr)\n", " assert not os.path.exists(addr), f\"{addr} in use\"\n", " s = socket.socket(family, typ)\n", + " if reuse_addr and family==socket.AF_INET: s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n", " s.bind(addr)\n", " s.listen(*listen_args)\n", " return s"