Ввод Callable с *args и аргументами, содержащими только ключевые словаPython

Программы на Python
Anonymous
Ввод Callable с *args и аргументами, содержащими только ключевые слова

Сообщение Anonymous »

Я пытаюсь создать систему, записывающую вызовы методов класса. (Я уже сталкивался с некоторыми проблемами, о которых писал вчера, и они были решены).
Идея следующая:

Код: Выделить всё

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}
Скрипт выполняется без проблем, но у mypy есть проблемы
Это сигнализирует мне, что оболочка имеет неправильный тип:

Код: Выделить всё

Incompatible return value type (got "Callable[[CountType, VarArg(Any), DefaultNamedArg(str | None, 'log_name'), KwArg(Any)], RetType]", expected "Callable[[CountType, str, **Param], RetType]")
Думаю, это потому, что я хотел, чтобы log_name был аргументом, содержащим только ключевые слова, поэтому понимаю вызов A(count_dict).test(2.0, log_name="testing")< /code> гораздо проще, чем если бы у нас был A(count_dict).test(2.0, "testing")
Я пытался использовать протокол для набора текста но тогда у меня были другие проблемы

Код: Выделить всё

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}
В этом случае строки A(count_dict).test(2.0) вызывают следующую ошибку:

Код: Выделить всё

Argument 1 to "__call__" of "CountableCallable" has incompatible type "float"; expected "A"
Вызов A(count_dict).test(multiplier=2.0, log_name="testing") вызывает следующую ошибку:

Код: Выделить всё

Missing positional argument "self" in call to "__call__"  of "CountableCallable"
Единственный рабочий вызов — это A.test(A(count_dict), 2.0), который не используется для вызова методов.
Я также пытался использовать декоратор вместо оболочки, но получал те же предупреждения
Я пытался поменять местами имена аргументов self и __self__ для CountableCallable.__call__ но он ничего не сделал
Поэтому мне было интересно, существует ли вариант протокола, который позволил бы мне указать, что CountableCallable является методом класса (так что возможно, у нас не было бы путаницы)
Или, если бы был способ использовать typing.Callable, уточнив, что имя_журнала является аргументом только для ключевого слова

Подробнее здесь: https://stackoverflow.com/questions/788 ... -only-args

Вернуться в «Python»