Skip to content

Instantly share code, notes, and snippets.

@rodion-solovev-7
Last active February 2, 2022 17:22
Show Gist options
  • Save rodion-solovev-7/9d359a731fc2e8b86d02892ce78c4602 to your computer and use it in GitHub Desktop.
Save rodion-solovev-7/9d359a731fc2e8b86d02892ce78c4602 to your computer and use it in GitHub Desktop.
Декоратор, подменяющий код, но сохраняющий сигнатуру функции
import functools
import inspect
from typing import TypeVar, Callable
RetT = TypeVar("RetT")
def replace_code_and_keep_signature(func: Callable[..., RetT]) -> Callable[..., RetT]:
"""Декоратор, который создаёт функцию с эквивалентной сигнатурой, но другим кодом.
Идея решения отсюда:
https://stackoverflow.com/a/60638430
Возможно, есть другое (!) хардкорное (!) решение через types.FunctionType и types.CodeType:
https://stackoverflow.com/a/16123158
"""
sig = inspect.signature(func)
@functools.wraps(func)
def wrapper(*args, **kwargs):
bound = sig.bind(*args, **kwargs) # compute the bound parameter list
bound.apply_defaults()
# Здесь делаем, что хотим. Аргументы достаём из bound.args, bound.kwargs
return f"Wrapped fucntion call with args: {bound=}"
wrapper.__signature__ = sig
return wrapper
def f(a: int, /, b: str, *, c: object, d: float = 0.8) -> str:
return "Orig function call"
# Вне тестов используется как обычный декоратор:
#
# @replace_code_and_keep_signature
# def f(...) -> ...:
# ...
g = replace_code_and_keep_signature(f)
orig_sig = inspect.signature(f)
wrap_sig = inspect.signature(g)
assert orig_sig == wrap_sig, f"Сигнатуры не совпадают: orig=[{orig_sig}] wrap=[{wrap_sig}]"
args = [0, 'Hello World']
kwargs = dict(c=42, d=3.14)
print(f(*args, **kwargs))
# Orig function call
print(g(*args, **kwargs))
# Wrapped fucntion call with args: bound=<BoundArguments (a=0, b='Hello World', c=42, d=3.14)>
# Usage:
# g(a=1, b=2, c=3, d=4)
# TypeError: 'a' parameter is positional only, but was passed as a keyword
g(1, b=2, c=3, d=4)
# Работает (хоть типы аргументов и неверные)
g(1, 2, c=3, d=4)
# Работает (хоть типы аргументов и неверные)
# g(1, 2, 3, d=4)
# TypeError: too many positional arguments
# g()
# TypeError: missing a required argument: 'a'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment