Last active
February 2, 2022 17:22
-
-
Save rodion-solovev-7/9d359a731fc2e8b86d02892ce78c4602 to your computer and use it in GitHub Desktop.
Декоратор, подменяющий код, но сохраняющий сигнатуру функции
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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