【问题标题】:Python module dependencyPython 模块依赖
【发布时间】:2010-09-14 13:39:35
【问题描述】:

好的,我有两个模块,每个模块都包含一个类,问题是它们的类相互引用。

假设我有一个房间模块和一个包含 CRoom 和 CPerson 的人员模块。

CRoom 类包含有关房间的信息,以及房间中每个人的 CPerson 列表。

但是,CPerson 类有时需要使用 CRoom 类作为其所在的房间,例如寻找门,或者查看房间里还有谁。

问题在于两个模块相互导入我只是得到一个导入错误,第二个导入的是哪个模块:(

在 c++ 中,我可以通过只包含标题来解决这个问题,因为在这两种情况下,类都只有指向另一个类的指针,所以前向声明就足够了,例如:

class CPerson;//forward declare
class CRoom
{
    std::set<CPerson*> People;
    ...

除了将两个类放在同一个模块或类似的东西中之外,在 python 中还有其他方法吗?

编辑:添加了显示使用上述类的问题的python示例

错误:

回溯(最近一次通话最后一次):
文件“C:\Projects\python\test\main.py”,第 1 行,在
从房间导入 C Room

中的文件“C:\Projects\python\test\room.py”,第 1 行 从人导入 CPerson

中的文件“C:\Projects\python\test\person.py”,第 1 行 从房间导入 C Room
ImportError: 无法导入名称 C Room
房间.py

from person import CPerson

class CRoom:
    def __init__(Self):
        Self.People = {}
        Self.NextId = 0

    def AddPerson(Self, FirstName, SecondName, Gender):
        Id = Self.NextId
        Self.NextId += 1#

        Person = CPerson(FirstName,SecondName,Gender,Id)
        Self.People[Id] = Person
        return Person

    def FindDoorAndLeave(Self, PersonId):
        del Self.People[PeopleId]

person.py

from room import CRoom

class CPerson:
    def __init__(Self, Room, FirstName, SecondName, Gender, Id):
        Self.Room = Room
        Self.FirstName = FirstName
        Self.SecondName = SecondName
        Self.Gender = Gender
        Self.Id = Id

    def Leave(Self):
        Self.Room.FindDoorAndLeave(Self.Id)

【问题讨论】:

  • 你能发布一个小测试用例来重现你的错误吗?我尝试创建两个相互引用并且没有问题的模块,所以我认为我遗漏了一些微妙的点。
  • [offtop] 请阅读 Python 风格指南 python.org/dev/peps/pep-0008 。特别是,从类名中删除第一个“C”,示例中的所有其他名称都应为小写。要回答您的问题:只需 import room 并在 Person 的方法中使用 room.Room(...)。
  • 提及您使用的 python 版本可能会很有用。我认为这对于某些版本的 python 3 来说不是问题(我认为是 3.5 但不是 3.4)。

标签: python module circular-dependency


【解决方案1】:

你可以给第二个起别名。

import CRoom

CPerson = CRoom.CPerson

【讨论】:

  • 那行不通,就像在模块顶部完成一样,CRoom.CPerson 可能还不存在。你能做到的唯一方法是将你的名字从另一个模块(即import croom; croom.CPerson = CPerson)插入其他模块名称空间,这非常hacky。最好使用完全限定名称。
【解决方案2】:

首先,用大写字母命名参数会造成混淆。由于 Python 没有正式的静态类型检查,我们使用 UpperCase 表示类,使用 lowerCase 表示参数。

其次,我们不理会 CRoom 和 CPerson。大写足以表明它是一个类。不使用字母 C。 Room。 Person.

第三,我们通常不以每个文件一个类格式放置内容。文件是一个 Python 模块,我们更经常导入包含所有类和函数的整个模块。

[我知道这些都是习惯——你今天不需要打破它们,但它们确实让人难以阅读。]

Python 不使用像 C++ 这样的静态定义类型。当您定义一个方法函数时,您并没有正式定义该函数参数的数据类型。您只需列出一些变量名称。希望客户端类能够提供正确类型的参数。

在运行时,当您发出方法请求时,Python 必须确保对象具有该方法。笔记。 Python 不会检查对象是否是正确的类型——这无关紧要。它只检查它是否有正确的方法。

room.Room 和 person.Person 之间的循环是个问题。定义另一个时不需要包含一个。

导入整个模块是最安全的。

这里是room.py

import person
class Room( object ):
    def __init__( self ):
        self.nextId= 0
        self.people= {}
    def addPerson(self, firstName, secondName, gender):
        id= self.NextId
        self.nextId += 1

        thePerson = person.Person(firstName,secondName,gender,id)
        self.people[id] = thePerson
        return thePerson 

只要 Person 最终在执行它的命名空间中定义,就可以正常工作。定义类时不必知道人员。

直到运行时才知道 Person(...) 表达式,然后计算 Person(...) 表达式。

这里是person.py

import room
class Person( object ):
    def something( self, x, y ):
        aRoom= room.Room( )
        aRoom.addPerson( self.firstName, self.lastName, self.gender )

您的main.py 看起来像这样

import room
import person
r = room.Room( ... )
r.addPerson( "some", "name", "M" )
print r

【讨论】:

    【解决方案3】:

    @S.洛特 如果我没有将任何内容导入到房间模块中,我会得到一个未定义的错误(我将它导入到主模块中,就像你展示的那样)

    回溯(最近一次通话最后一次):

    中的文件“C:\Projects\python\test\main.py”,第 6 行 Ben = Room.AddPerson('Ben', 'Blacker', 'Male')
    AddPerson
    中的文件“C:\Projects\python\test\room.py”,第 12 行 Person = CPerson(FirstName,SecondName,Gender,Id)
    NameError:未定义全局名称“CPerson”

    另外,存在不同模块的原因是我遇到的问题是从容器类(例如房间)开始已经有几百行,所以我希望其中的项目(例如人)在一个单独的文件中。

    编辑: main.py

    from room import CRoom
    from person import CPerson
    
    Room = CRoom()
    
    Ben = Room.AddPerson('Ben', 'Blacker', 'Male')
    Tom = Room.AddPerson('Tom', 'Smith',   'Male')
    
    Ben.Leave()
    

    【讨论】:

    • 错误的根本原因在 main.主要是什么样子的?它会导入 Room 和 Person 吗?
    • 谢谢。我对范围规则的错误。每个模块都有自己的命名空间。 Room 在 Room 模块命名空间中执行。
    • main为什么要导入CPerson?它不使用它。如果只通过 Room 实例创建人员,则只需在 room.py 中导入人员即可。
    【解决方案4】:

    无需导入CRoom

    你没有在person.py 中使用CRoom,所以不要导入它。由于动态绑定,Python 不需要“在编译时查看所有类定义”。

    如果你确实做在person.py 中使用CRoom,然后将from room import CRoom 更改为import room 并使用模块限定形式room.CRoom。详情请见Effbot's Circular Imports。

    旁注:您可能在Self.NextId += 1 行中有错误。它增加实例的NextId,而不是类的NextId。要增加类的计数器,请使用 CRoom.NextId += 1 或 Self.__class__.NextId += 1。

    【讨论】:

    • 那么,如果他们对自己所在的房间没有参考,那么这个人怎么能看到房间里还有谁,或者找到门等呢?
    • 只需删除“从房间导入 C Room”。您仍然可以将 C Room 实例从外部传递到 person.py 中。您将无法从 person.py 调用 CRoom 构造函数,但无论如何您都不会这样做。
    • 第二个命题不起作用。你导入的是模块而不是类,所以你会得到AttributeError: 'module' object has no attribute。
    【解决方案5】:

    您真的需要在类定义时引用这些类吗?即。

     class CRoom(object):
         person = CPerson("a person")
    

    或者(更有可能),您是否只需要在您的类的方法中使用 CPerson(反之亦然)。例如:

    class CRoom(object):
        def getPerson(self): return CPerson("someone")
    

    如果是第二个,则没有问题 - 因为当方法被调用而不是定义时,模块将被导入。您唯一的问题是如何引用它。您可能正在做类似的事情:

    from CRoom import CPerson # or even import *
    

    使用循环引用模块,您不能这样做,因为在一个模块导入另一个模块时,原始模块主体不会完成执行,因此命名空间将是不完整的。相反,使用合格的引用。即:

    #croom.py
    import cperson
    class CRoom(object):
        def getPerson(self): return cperson.CPerson("someone")
    

    这里,python 不需要在命名空间上查找属性,直到方法实际被调用,此时两个模块都应该完成它们的初始化。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-25
      • 2016-09-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-23
      • 2019-02-11
      相关资源
      最近更新 更多