Funchacks is a fun module that provides a small package of utilities.
Dynamic signature change without compile, eval and exec? That was the main idea of the project! But this path is a little dangerous, so the part could not be implemented, but if possible it will be implemented in the next versions!
pip install funchacks
from funchacks import inspections
def foo() -> None:
some_local_var = 1
other_var = 2
>>> dict(inspections.getlocals(foo.__code__))
{"some_local_var": 1, "other_var": 2}
(!)
Note: if you add positional only or positional arguments, then there must be*args
in the function signature. Accordingly, if you add keyword only or keyword arguments -**kwargs
.
import inspect
from typing import Any
from funchacks import sig
@sig.change_args(
sig.posonly("first"),
sig.arg("second"),
)
def foo(*args: Any) -> None:
"""
!!! Note:
Temporarily positional only arguments are available only for
the signature, there may be errors when calling the function.
"""
>>> inspect.Signature.from_callable(foo)
(first, /, second, *args)
@sig.change_args(
sig.kwarg("first", None),
sig.kwonly("second"),
)
def bar(**kwargs: Any) -> None:
"""
!!! Note:
Temporarily keyword only arguments are available only for
the signature, there may be errors when calling the function.
"""
>>> inspect.Signature.from_callable(bar)
(first=None, *, second, **kwargs)
@sig.change_args(
sig.arg("first"),
sig.kwarg("second", None)
)
def baz(*args: Any, **kwargs: Any) -> None:
"""This should work.
But how to access the arguments? locals?...
"""
# All wrapped function has __sig__ attribute
# that contains function signature.
lvars = sig.Bind.from_locals(locals(), in_=baz)
assert lvars.args() == ["first"]
assert lvars.kwargs() == ["second"]
return lvars.get("first") + lvars.get("second")
>>> inspect.Signature.from_callable(baz)
(first, second=None, *args, **kwargs)
>>> baz(1, 2)
3
def spam(a, /, b, c=None, *, d) -> None:
pass
@sig.from_function(spam)
def eggs(*args: Any, **kwargs: Any) -> None:
pass
>>> inspect.Signature.from_callable(eggs)
(a, /, b, c=None, *, d)
from types import CodeType
from funchacks import make_wrap, WrapFunction
def foo() -> None:
some_local = 1
other_local = 2
wrap = make_wrap(foo)
wrap_from_func = WrapFunction.from_function(foo)
>>> dict(wrap.flocals)
{'some_local': 1, 'other_local': 2}
>>> isinstance(wrap, WrapFunction)
True
>>> dict(wrap.flocals) == dict(wrap_from_func.flocals)
True
>>> isinstance(wrap.code, CodeType)
True