funchacks
👋
Introduction
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!
So is it worth using funchacks signature utilities? More likely no than yes. If you want a really optimized and safe implementation of this idea, it's better to look into makefun
(this was another reason why I wanted to do a dynamic signature change without compile, eval and exec).
⚙️
Installation
pip install funchacks
🚀
Quick start
-
🔎 Function locals
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}
-
🔗 Dynamic function signature
(!)
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
Signature from function.
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)
Making small wrap.
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