首页 > 编程问答 >具有自引用的类装饰器的 Python 类型提示

具有自引用的类装饰器的 Python 类型提示

时间:2024-07-31 08:31:29浏览次数:17  
标签:python python-typing mypy python-decorators

我的最终目标是编写一个系统来轻松记录函数调用(特别是类方法)。

我首先编写一个带有包装方法的类 Loggable ,该方法允许我装饰子类方法并记录它们的调用|| |现在我可以编写子类并记录它们的调用:

Param = ParamSpec("Param")
RetType = TypeVar("RetType")


CountType = TypeVar("CountType", bound="FunctionCount")


class FunctionCount(Generic[CountType]):
    def __init__(self, count_dict: dict[str, int]) -> None:
        self.count_dict = count_dict

    @staticmethod
    def count(
        func: Callable[Concatenate[CountType, Param], RetType],
    ) -> Callable[Concatenate[CountType, Param], RetType]:
        def wrapper(
            self: CountType, *args: Param.args, **kwargs: Param.kwargs
        ) -> RetType:
            function_name = 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):
    def __init__(self, count_dict: dict[str, int]) -> None:
        super().__init__(count_dict)

    @FunctionCount.count
    def func(self) -> None:
        pass

    @FunctionCount.count
    def func2(self) -> None:
        pass


count_dict: dict[str, int] = {}

a = A(count_dict)

a.func()
a.func()
a.func2()

print(count_dict)
assert count_dict == {"A.func": 2, "A.func2": 1}

然后我只需更改装饰器调用

class FunctionCount(Generic[CountType]):
    def __init__(self, count_dict: dict[str, int]) -> None:
        self.count_dict = count_dict

    @staticmethod
    def count(
        f_name: str | None = None,
    ) -> Callable[
        [Callable[Concatenate[CountType, Param], RetType]],
        Callable[Concatenate[CountType, Param], RetType],
    ]:
        def decorator(
            func: Callable[Concatenate[CountType, Param], RetType],
        ) -> Callable[Concatenate[CountType, Param], RetType]:
            def wrapper(
                self: CountType, *args: Param.args, **kwargs: Param.kwargs
            ) -> RetType:
                function_name = f_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

        return decorator

这个脚本也可以很好地工作,但现在 mypy 给了我困难时期。 当我调用

class A(FunctionCount):
    def __init__(self, count_dict: dict[str, int]) -> None:
        super().__init__(count_dict)

    @FunctionCount.count()
    def func(self) -> None:
        pass

    @FunctionCount.count("custom_name")
    def func2(self) -> None:
        pass

a.func()
a.func()
a.func2()

print(count_dict)
assert count_dict == {"A.func": 2, "custom_name": 1}

方法时,出现以下 mypy 错误: a.func 无效的自参数“A”到类型为“Callable[[Never], None]”的属性函数“func” mypy(misc)

我猜想使用装饰器而不是包装器导致了这个错误,但我不明白为什么以及我应该做什么来纠正它。

有人知道如何以这种方式拥有正确类型的装饰器吗?

Does someone know how to have a correctly typed decorator this way ?


遇到的 mypy 错误是由于在装饰器定义中, CountType 的类型变量没有正确地从方法定义传递到装饰器内部。以下是如何解决此问题并改进类型提示的方法:

from typing import Callable, Concatenate, ParamSpec, TypeVar, Generic, overload

Param = ParamSpec("Param")
RetType = TypeVar("RetType")

class FunctionCount(Generic[RetType]):
    def __init__(self, count_dict: dict[str, int]) -> None:
        self.count_dict = count_dict

    @overload
    @staticmethod
    def count(
        f_name: str,
    ) -> Callable[
        [Callable[Concatenate['FunctionCount', Param], RetType]], 
        Callable[Concatenate['FunctionCount', Param], RetType]
    ]: ...

    @overload
    @staticmethod
    def count(
    ) -> Callable[
        [Callable[Concatenate['FunctionCount', Param], RetType]], 
        Callable[Concatenate['FunctionCount', Param], RetType]
    ]: ...

    @staticmethod
    def count(
        f_name: str | None = None,
    ) -> Callable[
        [Callable[Concatenate['FunctionCount', Param], RetType]], 
        Callable[Concatenate['FunctionCount', Param], RetType]
    ]:
        def decorator(
            func: Callable[Concatenate['FunctionCount', Param], RetType],
        ) -> Callable[Concatenate['FunctionCount', Param], RetType]:
            def wrapper(
                self: 'FunctionCount', *args: Param.args, **kwargs: Param.kwargs
            ) -> RetType:
                function_name = f_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
        return decorator

class A(FunctionCount):
    def __init__(self, count_dict: dict[str, int]) -> None:
        super().__init__(count_dict)

    @FunctionCount.count()
    def func(self) -> None:
        pass

    @FunctionCount.count("custom_name")
    def func2(self) -> None:
        pass

count_dict: dict[str, int] = {}

a = A(count_dict)

a.func()
a.func()
a.func2()

print(count_dict)  # 输出: {'A.func': 2, 'custom_name': 1}

解释:

  1. 移除 CountType : 我们不需要 CountType 类型变量,因为我们总是在 FunctionCount 类中使用它。
  2. FunctionCount 作为第一个参数: 我们将 FunctionCount 类本身作为装饰器函数的第一个参数传递给 Concatenate ,因为我们希望跟踪该类的方法调用。
  3. 使用 overload : 我们使用 @overload 装饰器来提供两种不同的类型提示,一种用于带有名稱参数的装饰器,另一种用于不带名称参数的装饰器。
  4. 明确的类型提示: 我们在整个代码中提供了更清晰、更明确的类型提示,以帮助 mypy 更好地理解代码。

通过这些更改,mypy 将不再报告错误,并且的代码将具有更强大的类型安全性。

标签:python,python-typing,mypy,python-decorators
From: 78812667

相关文章

  • 如何在for循环中使用curve_fit函数在python中一次性创建多个回归?
    简而言之,我有两个矩阵,一个称为t,另一个称为y。每个都有7列。假设它们被称为a、b、c、d、e、f和g。我想要的是从a对a、b对b、...、g对g这两个矩阵进行回归。我已经设法使我的算法使用curve_fit对一列进行回归一次。但我真正希望的是它能够一次性完成7个回归......
  • 激活虚拟环境会让python消失?
    VisualStudioCode终端的屏幕截图如屏幕截图所示,python在Powershell中运行得很好。然后我在E:\DrewFTCAPI\ftcapivenv激活虚拟环境,然后python就消失了。不仅没有消失,它不运行任何东西,也不产生任何输出。我至少预计会出现某种类型的"python"i......
  • Python 3.6 中的相互递归类型,使用命名元组语法
    我正在尝试实现图的节点和边。这是我的代码:fromtypingimportNamedTuple,ListclassNode(NamedTuple):name:stredges:List[Edge]classEdge(NamedTuple):src:Nodedest:Node这会引发错误,因为创建Edge时未定义Node类型。......
  • 使用 keras 模型对函数进行 Python 类型提示
    如果我创建这样的函数:defmdl(input_shape):model=Sequential()model.add(Conv2D(depth=64,kernel_size=(3,3),input_shape=input_shape,activation='relu'))model.add(Dense(32),activation='relu')model.add(Dropout(0.3))m......
  • Python:自动完成可以用于列表中的元素吗?
    Python在函数参数和函数返回类型中具有类型提示。类的元素是否有类似的东西?我希望能够在如下示例中使用自动完成功能:classMyClass:defhello(self):print("Hello")mylist=[]mylist.append(MyClass())foriinmylist:i.hello()#Noautocomplete......
  • python 中 COM 对象的正确类型提示是什么?
    我在python中使用COM对象来向3rd方软件公开可编程接口。这是通过使用Dispatchfromwin32com.client来实现的。我的项目也一直在使用python.3.7中的类型提示,但是我不确定如何为了类型提示的目的定义这些COM对象的类型。这个问题涉及我拥有的所有COM......
  • 如何遍历Python字典同时避免KeyErrors?
    解析大型JSON时,某些键可能仅在某些情况下存在,例如出现错误时。从服务器的API获取200OK的情况并不少见,但是您得到的响应包含应检查的错误。处理此问题的最佳方法是什么?我知道使用类似||之类的东西。|是处理KeyError的一种方法。get()但是如果......
  • Python 中的递归数据类型
    Python中最接近Haskell中的递归数据类型的是什么?(即在定义自身时使用类型自己的定义。)编辑:为了给出递归类型的更具体定义,下面是Haskell中的二叉树:dataTreea=Leafa|Branch(Treea)(Treea)我的阅读方式如下:二叉树可以是叶子,也可以包含两......
  • 如何在Python中平滑相邻的多边形?
    我正在寻找一种平滑多边形的方法,以便相邻/接触的多边形保持接触。单个多边形可以轻松平滑,例如使用PAEK或Bezier插值(https://pro.arcgis.com/en/pro-app/latest/tool-reference/cartography/smooth-polygon.htm),这自然会改变它们的边界边缘。但是如何平滑所有多边形......
  • Python多处理池不启动多个进程
    我正在尝试使用多处理池来创建多个进程。我有一个工作函数dummy_proc定义如下:importrefrommultiprocessingimportPooldefregex_check(input_string):#Patterntomatchboth"pm_lat"and"pm_lon_coslat"followedbytwofloatspattern=r"(c......