【问题标题】:Pythonic way to resolve circular import statements?解决循环导入语句的 Pythonic 方法?
【发布时间】:2011-04-21 19:37:41
【问题描述】:

我只是继承了一些让我感到不安的代码:有一个测试库,里面充满了与我们网站上的网页相对应的类,并且每个网页类都有方法来自动化该页面上的功能。

有方法可以点击页面之间的链接,返回链接页面的类。这是一个简化的示例:

文件 homePageLib.py:

class HomePage(object):
    def clickCalendarLink(self):
        # Click page2 link which navigates browswer to page2
        print "Click Calendar link"
        # Then returns the page2 object
        from calendarLib import CalendarPage
        return CalendarPage()

文件calendarLib.py:

class CalendarPage(object):
    def clickHomePageLink(self):
        # Click page1 link which navigates browswer to page1
        print "Click Home Page link"
        # Then return the page2 object
        from homePageLib import HomePage
        return HomePage()

然后,这允许脚本文件单击页面并从该方法获取对象作为返回值,这意味着脚本作者在浏览站点时不必继续实例化新页面。 (我觉得这是一个奇怪的设计,但我无法确切说明原因,除了有一个名为“clickSomeLink”的方法并返回结果页面的对象似乎很奇怪。)

以下脚本说明了脚本如何在站点中导航:(我插入了print page 以显示页面对象如何更改)

脚本文件:

from homePageLib import HomePage

page = HomePage()    
print page
page = page.clickCalendarLink()
print page
page = page.clickHomePageLink()
print page

产生以下输出:

<homePageLib.HomePage object at 0x00B57570>
Click Calendar link
<calendarLib.CalendarPage object at 0x00B576F0>
Click Home Page link
<homePageLib.HomePage object at 0x00B57570>

所以,我特别感到最不安的部分是 from ____ import ____ 行结束了。这些让我觉得很糟糕,原因如下:

  1. 我一直将所有导入语句放在文件的顶部。
  2. 由于一个页面可能有多个链接,这会导致文件中多个位置出现相同的from foo import bar 代码行。

问题是,如果我们将这些导入语句放在页面顶部,则会出现导入错误,因为(根据此示例),HomePage 导入 CalendarPage,反之亦然:

文件 homePageLib.py

from calendarLib import CalendarPage

class HomePage(object):
    def clickCalendarLink(self):
        # Click page2 link which navigates browswer to page2
        print "Click Calendar link"
        # Then returns the page2 object

        return CalendarPage()

文件 calendarLib.py

from homePageLib import HomePage

class CalendarPage(object):
    def clickHomePageLink(self):
        # Click page1 link which navigates browswer to page1
        print "Click Home Page link"
        # Then return the page2 object
        return HomePage()

这会导致以下错误:

>>> from homePageLib import HomePage
Traceback (most recent call last):
  File "c:\temp\script.py", line 1, in ?
    #Script
  File "c:\temp\homePageLib.py", line 2, in ?
    from calendarLib import CalendarPage
  File "c:\temp\calendarLib.py", line 2, in ?
    from homePageLib import HomePage
ImportError: cannot import name HomePage

(关于如何更好地格式化 python 输出的提示?)

与其延续这种风格,我想找到更好的方法。有没有一种 Python 的方式来处理这样的循环依赖,并且仍然将 import 语句保留在文件的顶部?

【问题讨论】:

    标签: python pageobjects


    【解决方案1】:

    解决这些结构通常涉及Dependency Injection 等技术。

    然而,修复这个错误相当简单:

    在calendarLib.py中:

    import homePageLib
    
    class CalendarPage(object):
        def clickHomePageLink(self):
            [...]
            return homePageLib.HomePage()
    

    模块级别的代码在导入时执行。使用from [...] import [...] 语法需要模块完全初始化才能成功。

    一个简单的import [...] 不会,因为没有符号被访问,从而破坏了依赖链。

    【讨论】:

    • 我明白了。为什么这样行得通? homePageLib 仍然导入 calendarPageLib,反之亦然。
    • @nathan:请查看此以获取更多信息。 stackoverflow.com/questions/710551/…
    • 这适用于所有循环导入问题吗?因为它没有解决我的问题。
    • 但是如果clickHomePageLink 有返回值类型提示呢?这意味着返回值类型在外面?
    【解决方案2】:

    详细解释请阅读Sebastian's answer。这种方法是由 David Beazley 在 PyCon

    中提出的

    尝试像这样将导入放置在顶部

    try:
        from homePageLib import HomePage
    except ImportError:
        import sys
        HomePage = sys.modules[__package__ + '.HomePage']
    

    这将尝试导入您的HomePage,如果失败,将尝试从缓存中加载它

    【讨论】:

    • 为什么这个多行结构在 Python 标准库中没有内置快捷方式?
    • 这看起来很老套,但实际上是明确的(阅读:遵循 PEP 20)。很好!
    • HomePage 是问题中的一个类,但这个答案将其视为一个模块。
    【解决方案3】:

    我有一个循环导入,因为我在类型提示中引用了一个类。 这可以使用from __future__ import annotations 解决(使用 Python 3.9.x 测试)。

    例子:

    AClass.py

    from BClass import BClass
    class AClass():
        def __init__(self) -> None:
            self.bClass = BClass(self)
    

    BClass.py

    from __future__ import annotations  # Without this, the type hint below would not work.
    import AClass  # Note that `from AClass import AClass` would not work here.`
    class BClass:
        def __init__(self, aClass: AClass.AClass) -> None:
            self.aClass = aClass
    

    【讨论】:

      猜你喜欢
      • 2023-03-29
      • 1970-01-01
      • 2011-10-22
      • 2018-06-06
      • 1970-01-01
      • 2018-04-28
      • 2016-10-10
      • 1970-01-01
      相关资源
      最近更新 更多