Идея следующая:
Код: Выделить всё
from collections.abc import Callable
from typing import Concatenate, ParamSpec, TypeVar
RetType = TypeVar("RetType")
Param = ParamSpec("Param")
CountType = TypeVar("CountType", bound="FunctionCount")
class FunctionCount:
def __init__(self, count_dict: dict[str, int]) -> None:
self.count_dict = count_dict
def count(
func: Callable[Concatenate[CountType, Param], RetType],
) -> Callable[Concatenate[CountType, str, Param], RetType]:
def wrapper(
self: CountType,
/,
*args: Param.args,
log_name: str | None = None,
**kwargs: Param.kwargs,
) -> RetType:
function_name = log_name or f"{self.__class__.__name__}.{func.__name__}"
if function_name not in self.count_dict:
self.count_dict[function_name] = 0
self.count_dict[function_name] += 1
return func(self, *args, **kwargs)
return wrapper
class A(FunctionCount):
@count
def test(self, multiplier: float) -> float:
return multiplier
count_dict = dict[str, int]()
A(count_dict).test(2.0)
A(count_dict).test(multiplier=2.0, log_name="testing")
print(count_dict)
assert count_dict == {"A.test": 1, "testing": 1}
Это сигнализирует мне, что оболочка имеет неправильный тип:
Код: Выделить всё
Incompatible return value type (got "Callable[[CountType, VarArg(Any), DefaultNamedArg(str | None, 'log_name'), KwArg(Any)], RetType]", expected "Callable[[CountType, str, **Param], RetType]")
Я пытался использовать протокол для набора текста но тогда у меня были другие проблемы
Код: Выделить всё
from collections.abc import Callable
from typing import Concatenate, ParamSpec, Protocol, TypeVar
RetType_co = TypeVar("RetType_co", covariant=True)
Param = ParamSpec("Param")
CountType_contra = TypeVar(
"CountType_contra", bound="FunctionCount", contravariant=True
)
class FunctionCount:
def __init__(self, count_dict: dict[str, int]) -> None:
self.count_dict = count_dict
class CountableCallable(Protocol[CountType_contra, Param, RetType_co]):
def __call__(
_self, # noqa: N805
self: CountType_contra,
/,
*args: Param.args,
log_name: str | None = None,
**kwargs: Param.kwargs,
) -> RetType_co: ...
def count(
func: Callable[Concatenate[CountType_contra, Param], RetType_co],
) -> CountableCallable[CountType_contra, Param, RetType_co]:
def wrapper(
self: CountType_contra,
/,
*args: Param.args,
log_name: str | None = None,
**kwargs: Param.kwargs,
) -> RetType_co:
function_name = log_name or f"{self.__class__.__name__}.{func.__name__}"
if function_name not in self.count_dict:
self.count_dict[function_name] = 0
self.count_dict[function_name] += 1
return func(self, *args, **kwargs)
return wrapper
class A(FunctionCount):
@count
def test(self, multiplier: float) -> float:
return multiplier
count_dict = dict[str, int]()
A(count_dict).test(2.0)
A(count_dict).test(multiplier=2.0, log_name="testing")
print(count_dict)
assert count_dict == {"A.test": 1, "testing": 1}
Код: Выделить всё
Argument 1 to "__call__" of "CountableCallable" has incompatible type "float"; expected "A"
Код: Выделить всё
Missing positional argument "self" in call to "__call__" of "CountableCallable"
Я также пытался использовать декоратор вместо оболочки, но получал те же предупреждения
Я пытался поменять местами имена аргументов self и __self__ для CountableCallable.__call__ но он ничего не сделал
Поэтому мне было интересно, существует ли вариант протокола, который позволил бы мне указать, что CountableCallable является методом класса (так что возможно, у нас не было бы путаницы)
Или, если бы был способ использовать typing.Callable, уточнив, что имя_журнала является аргументом только для ключевого слова
Подробнее здесь: https://stackoverflow.com/questions/788 ... -only-args