从不同模块层级导入类导致类型提示和参数警告禁用

0 投票
2 回答
24 浏览
提问于 2025-04-14 18:25

我有这样的文件结构:

 - package
   - sub_package_1
     - __init__.py
     - sub_module_1.py
   - sub_package_2
     - __init__.py
     - sub_module_2.py

sub_module_1.py 文件里,我们有这样的代码:

class A:
    def __init__(self):
        self.b = B()

    def method_a(self, var: int):
        pass


class B:
    def __init__(self):
        pass

    def method_b(self, var: int):
        pass



class C:  # This class always located in different module, but for example lets place it here
    def __init__(self):
        self.a = A()

    def method_c(self, var: int):
        pass


a = A()
a.b.method_b()  # here we have an argument warning (method_b is waiting for an argument)

c = C()
c.a.b.method_b()  # and also here, the same warning

而在 sub_module_2.py 文件里,我们有这样的代码:

from package.sub_package_1.sub_module_1 import A


class C:
    def __init__(self):
        self.a = A()

    def method_c(self, var: int):
        pass


c = C()
c.a.b.method_b()  # but here we don't have a warning

我在代码里留了一些注释来说明问题。
当我在同一个模块(或者同一个包)里工作时,我可以看到所有因为方法参数引起的警告。但是如果我把其中一个类移出这个包(就像我把 C 类移出去那样),我就看不到任何警告了。我仍然可以自动补全代码,有类型提示等等,但如果我在 c.a.b.method_b() 中没有传递任何参数,就不会有警告。

我觉得这可能是导入的问题,但我找不到解决办法。
这种情况的原因是什么,我该如何解决呢?

2 个回答

0

感谢一位聪明人的帮助,他指出了错误所在。

问题(其实不算问题,只是IDE的优化)是PyCharm有一个深度检查的限制,这个限制是可以设置的,但如果设置得太深,会导致程序运行很卡,所以这并不是一个好的解决办法。

真正的解决方法是不要忘记在每一行代码中添加类型提示。在当前的例子中,我们需要在 sub_module_2.py 文件中这样做:

from package.sub_package_1.sub_module_1 import A


class C:
    def __init__(self):
        self.a: A = A()  # Here I forgot to typehint the class
    ...

不过,更好的做法是在类的层级上添加类型提示,而不是在 __init__ 方法里,因为在某些深度的“检查层级”中,PyCharm会停止检查 __init__ 方法(这是为了优化,理解起来也没问题)。

所以,如果我们想让代码检查工具不漏掉任何警告和类型提示,应该在类的层级上添加类型提示,像这样:

from package.sub_package_1.sub_module_1 import A


class C:
    """
    So, even if this class call is too deep, 
    PyCharm will check class (as minimum) and spot the `a: A` hint at class level.
    That would not have happened, if hint was in the `__init__` method.
    """

    a: A  # Here is the feature

    def __init__(self):
        self.a = A()
    ...
0

根据你写的代码,c.a的类型被推断为Any,这意味着你可以对它做任何事情,mypy(还有其他工具)都会觉得没问题。这包括假设c.a有一个属性b,并且这个属性有一个不需要参数的方法method_b

之所以会这样,是因为你没有为各种__init__函数提供任何类型注解,这让mypy基本上忽略了这些函数和它们产生的任何东西。

如果你为__init__方法明确提供返回类型None,那么即使跨模块导入,你也应该能得到预期的错误提示。

class B:
    def __init__(self) -> None:
        pass

    def method_b(self, var: int):
        pass

撰写回答