具有类型提示的模块化类方法



在这个问题中如何将类的函数分离到多个文件中?最重要的答案是建议使用

from method_file import method

在类定义内部,在单独的文件中定义类方法。然而,对于像这样的类

my_number.py

class MyNumber:
def __init__(self):
self.x = 5
from my_method import my_method

my_method.py

def my_method(self):
print(self.x)

对于IDE来说,self指的是MyNumber对象是不清楚的。因此,代码完成(例如self.x)在my_method中不可用。self的类型提示可以解决此问题,即

my_method.py

from my_number import MyNumber
def my_method(self: MyNumber):
print(self.x)

但这导致了循环导入。

对于这种情况,有什么变通方法或最佳实践吗?

有一种方法将__future__导入与if TYPE_CHECKING子句相结合,以在运行时忽略类型注释;进口;仅从IDE的角度来看代码,以便可以完成代码。

示例:

my_number.py

class MyNumber:
def __init__(self):
self.x = 5
from my_method import my_method

my_method.py

from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from my_number import MyNumber
def my_method(self: MyNumber):
print(self.x)

使用from __future__ import annotations,我们推迟了对类型提示的评估——换句话说,即使我们实际上没有导入MyNumber,我们也可以键入提示my_method。这个行为本来是Python 3.10中的默认行为,但它被推迟了,所以我们现在需要这个导入。

现在,根据您的编辑器/IDE,您仍然会收到一条警告,抱怨MyNumber没有定义,并且它的方法/属性可能不会显示在自动完成中。以下是TYPE_CHECKING的作用:这只是一个恒定的False值,这意味着我们的子句是:

if False:
from my_number import MyNumber

换句话说,我们是";欺骗;IDE认为我们正在导入MyNumber,但实际上这一行从未执行过。因此,我们完全避免了循环进口。

这可能会让人觉得有点棘手,但它很有效:-)TYPE_CHECKING常量的全部目的是允许类型检查器完成他们的工作,而不是在运行时实际导入代码,并且以一种清晰的方式来完成(Python的Zen:应该有一种——最好只有一种——明显的方法来完成)。

这种方法在PyCharm中一直适用于我,我不确定其他IDE/编辑器。

对于这种情况,有什么变通方法或最佳实践吗?

最佳做法是不要这样做。如果方法实现是特定于类的,那么它应该是类定义的一部分。


如果一个方法不是特定于类的,则应在所有有效类型中定义它。协议适合表达这一点:

from typing import Protocol, Any
class HasX(Protocol):
x: Any  # might need a TypeVar for complex cases
def my_method(self: HasX):
print(self.x)

如果一个方法扩展了一个独立于其定义的类,则不应对其进行修补。使用functools.singledispatch从外部定义单个调度函数,这些函数在逻辑上类似于方法:

from functools import singledispatch
from my_number import MyNumber
# not imported into MyNumber
@singledispatch
def my_method(self):
raise NotImplementedError(f"no dispatch for {type(self}") 
@my_method.register
def _(self: MyNumber):
print(self.x)

通常,self关键字用于表示类的实例。键入提示self是没有意义的。其次,如果函数不是MyNumber对象的类的方法,则不能通过self访问实例变量x。

我建议两个选项来实现您想要的。您可以接受MyNumber对象作为my_method()函数的参数,也可以创建一个新类并继承MyNumber类。确保文件在同一目录中,否则更新文件2中的import语句。

选项#1

class MyNumber:
def __init__(self):
self.x = 5
def my_method(my_number: MyNumber):
print(my_number.x)
my_method(MyNumber())

选项#2

#my_number.py
class MyNumber:
def __init__(self):
self.x = 5
#my_method.py
from my_number import MyNumber
class MyMethod(MyNumber):
def __init__(self):
super().__init__()

def my_method(self):
print(self.x)
MyMethod().my_method()

我认为您在python中遇到了有关面向对象概念的问题。您的";my_method";函数不需要";self:MyNumber";作为一个参数,事实上,您需要创建MyNumber类的一个对象,因此该类将具有一个属性,即;x〃;由于您定义了";x〃;在MyNumber类的构造函数中。它看起来像这样:

#my_number.py
class MyNumber:
def __init__(self):
self.x = 5
#my_method.py
from my_number import MyNumber
def my_method():
mm = MyNumber()
print(mm.x)

最新更新