如何避免由python中的指针属性的类型提示引起的循环依赖

lev*_*eer 6 python annotations circular-dependency type-hinting cross-reference

考虑两个模块(在同一文件夹中):

首先,person.py

from typing import List
from .pet import Pet


class Person:
    def __init__(self, name: str):
        self.name = name
        self.pets = [] # type: List[Pet]

    def adopt_a_pet(self, pet_name: str):
        self.pets.append(Pet(pet_name))
Run Code Online (Sandbox Code Playgroud)

然后是pet.py

from .person import Person


class Pet:
    def __init__(self, name: str, owner: Person):
        self.name = name
        self.owner = owner
Run Code Online (Sandbox Code Playgroud)

由于循环依赖,上述代码无法正常工作。您会得到一个错误:

ImportError: cannot import name 'Person'
Run Code Online (Sandbox Code Playgroud)

使其工作的一些方法:

  1. 将类Person和Pet的定义保存在同一文件中。
  2. 删除pet.owner属性(方便使用的指针)
  3. 不要在会引起循环引用的地方使用type-hinting / annotation:

例如:

class Pet:
    def __init__(self, name: str, owner):
Run Code Online (Sandbox Code Playgroud)

到目前为止,我列出的所有选项中都有一些缺点。

还有另一种方法吗?一个让我能够

  • 将类拆分为不同的文件
  • 将类型注释与指针结合使用,如图所示

或者:是否有充分的理由改而采用我已经列出的解决方案之一?

Gor*_*don 10

我最近遇到了类似的问题,并通过使用以下方法解决了它:

import typing

if typing.TYPE_CHECKING:
    from .person import Person


class Pet:
    def __init__(self, name: str, owner: 'Person'):
        self.name = name
        self.owner = owner
Run Code Online (Sandbox Code Playgroud)

有所述的第二溶液这里,这需要Python> = 3.7 && <3.10。

from __future__ import annotations  # <-- Additional import.
import typing

if typing.TYPE_CHECKING:
    from .person import Person


class Pet:
    def __init__(self, name: str, owner: Person):  # <-- No more quotes.
        self.name = name
        self.owner = owner
Run Code Online (Sandbox Code Playgroud)

从 Python 3.10 开始,__future__将不再需要导入。

  • Person 周围的撇号似乎是可选的。如果有人能解释带或不带撇号的区别,那就太好了。 (2认同)

lev*_*eer 0

经过更多学习后,我意识到有一种正确的方法可以做到这一点:继承:

首先,我定义 Person,不带 [pets] 或 OP 中的方法。然后我定义 Pets,并拥有 Person 类的所有者。然后我定义

from typing import List
from .person import Person
from .pet import Pet


class PetOwner(Person):
    def __init__(self, name: str):
        super().__init__(name)
        self.pets = []  # type: List[Pet]


    def adopt_a_pet(self, pet_name: str):
        self.pets.append(Pet(pet_name))
Run Code Online (Sandbox Code Playgroud)

Person 中需要引用 Pet 的所有方法现在都应该在 PetOwner 中定义,并且 Pet 中使用的 Person 的所有方法/属性都需要在 Person 中定义。如果需要使用 Pet 中仅存在于 PetOwner 中的方法/属性,则应定义 Pet 的新子类,例如 OwnedPet。

当然,如果命名令我烦恼,我可以将 Person 和 PetOwner 分别更改为 BasePerson 和 Person 或类似的名称。

  • 这是适合您的用例的一种可能的解决方法,但不能解决问题提出的问题。继承并不总是适合数据的最佳模型,但仍然需要解决类型检查引入的循环依赖关系。(不过我对这个问题投了赞成票)。 (4认同)