从不同模块层级导入类导致类型提示和参数警告禁用
我有这样的文件结构:
- 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 个回答
感谢一位聪明人的帮助,他指出了错误所在。
问题(其实不算问题,只是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()
...
根据你写的代码,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