class ParentClass:
# This class can contain the nasty definitions necessary
# to keep child classes nice looking
@abstractmethod
def run(self):
raise NotImplementedError()
class MyClass(ParentClass):
# I don't want to use `metaclass=MetaClass` here
# Define the logic in the method `run(...)`,
# signature can vary in different child classes
# Trying to avoid the need to use the `__call__` dunder
def run(self, a: int = 5) -> int:
print("my", a)
return 1
class MyClass2(ParentClass):
def run(self, b: str = "", c: bool = False) -> int:
print("my2", b, c)
return 2
from abc import abstractmethod
from functools import wraps
from typing import Callable, TypeVar, ParamSpec, Self, Any, cast
from __future__ import annotations
P = ParamSpec("P")
T = TypeVar("T")
class ParentMeta(type):
def __new__(cls: Self, name: str, bases: tuple, namespace: dict[str, Any]) -> ParentMeta:
def wrapper(func: Callable[P, T]) -> Callable[P, T]:
@wraps(func)
def inner(self, *args: P.args, **kwargs: P.kwargs) -> T:
print("--pre--")
ret = func(self, *args, **kwargs)
print("--post--")
return ret
return inner
if "run" in namespace:
namespace["__call__"] = wrapper(namespace["run"])
del namespace["run"]
return cast(ParentMeta, super().__new__(cls, name, bases, namespace))
class ParentClass(object, metaclass=ParentMeta):
@abstractmethod
def run(self):
raise NotImplementedError()
class MyClass(ParentClass):
def run(self, a: int = 5) -> int:
print("my", a)
return 1
my = MyClass()
my(a=6)
# the problem here is that I lost the parameter/type hint in the IDE (I'm using VSCode)
# (so when I'm writing `my(`, I cannot see that it has an argument `a`)
Если я откажусь от выполнения и определю __call__ (чего мне действительно не хочется), я получу подсказку типа (поскольку она решена из MyClass):
from abc import abstractmethod
from functools import wraps
from typing import Callable, TypeVar, ParamSpec, Self, Any, cast
from __future__ import annotations
P = ParamSpec("P")
T = TypeVar("T")
class ParentMeta(type):
def __new__(cls: Self, name: str, bases: tuple, namespace: dict[str, Any]) -> ParentMeta:
def wrapper(func: Callable[P, T]) -> Callable[P, T]:
@wraps(func)
def inner(self, *args: P.args, **kwargs: P.kwargs) -> T:
print("--pre--")
ret = func(self, *args, **kwargs)
print("--post--")
return ret
return inner
if "__call__" in namespace:
namespace["__call__"] = wrapper(namespace["__call__"])
return cast(ParentMeta, super().__new__(cls, name, bases, namespace))
class ParentClass(object, metaclass=ParentMeta):
@abstractmethod
def __call__(self):
raise NotImplementedError()
class MyClass(ParentClass):
def __call__(self, a: int = 5) -> int:
print("my", a)
return 1
my = MyClass()
my(a=6) # got the hint `(a: int = 5) -> int` when typed
Могу ли я написать вышеизложенное таким образом, чтобы мне не приходилось использовать __call__ в MyClass и при этом получать правильную подсказку о типе использования?
Я открыт для любых более простых/сложных решений и других версий Python (используется 3.11.10).
Как я хочу определить дочерние классы (т. е. MyClass сейчас): [code]class ParentClass: # This class can contain the nasty definitions necessary # to keep child classes nice looking @abstractmethod def run(self): raise NotImplementedError()
class MyClass(ParentClass): # I don't want to use `metaclass=MetaClass` here
# Define the logic in the method `run(...)`, # signature can vary in different child classes # Trying to avoid the need to use the `__call__` dunder def run(self, a: int = 5) -> int: print("my", a) return 1
class MyClass2(ParentClass): def run(self, b: str = "", c: bool = False) -> int: print("my2", b, c) return 2 [/code] Как я хочу использовать дочерние классы: [code]my = MyClass() my(a=6) # prints `my 6` and returns `1`
my2 = MyClass2() my2(b="example", c=True) # prints `my2 example True` and returns `2` [/code] Моя причина вышесказанного — обернуть методы запуска и выполнять действия до и после его вызова. Я пробовал следующее: [code]from abc import abstractmethod from functools import wraps from typing import Callable, TypeVar, ParamSpec, Self, Any, cast from __future__ import annotations
class ParentClass(object, metaclass=ParentMeta): @abstractmethod def run(self): raise NotImplementedError()
class MyClass(ParentClass): def run(self, a: int = 5) -> int: print("my", a) return 1
my = MyClass() my(a=6) # the problem here is that I lost the parameter/type hint in the IDE (I'm using VSCode) # (so when I'm writing `my(`, I cannot see that it has an argument `a`) [/code] Если я откажусь от выполнения и определю __call__ (чего мне действительно не хочется), я получу подсказку типа (поскольку она решена из MyClass): [code]from abc import abstractmethod from functools import wraps from typing import Callable, TypeVar, ParamSpec, Self, Any, cast from __future__ import annotations
class ParentClass(object, metaclass=ParentMeta): @abstractmethod def __call__(self): raise NotImplementedError()
class MyClass(ParentClass): def __call__(self, a: int = 5) -> int: print("my", a) return 1
my = MyClass() my(a=6) # got the hint `(a: int = 5) -> int` when typed [/code] Могу ли я написать вышеизложенное таким образом, чтобы мне не приходилось использовать __call__ в MyClass и при этом получать правильную подсказку о типе использования? Я открыт для любых более простых/сложных решений и других версий Python (используется 3.11.10).