Pythonic 方式来解决循环导入语句?

发布于 2024-11-03 08:02:29 字数 2828 浏览 6 评论 0原文

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

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

File 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()

File 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>

所以,我特别感到最不安的部分about 是最终结束的 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 输出的提示?)

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

I just inherited some code which makes me uneasy: There is a testing library, full of classes corresponding to webpages on our site, and each webpage class has methods to automate the functionality on that page.

There are methods to click the link between pages, which returns the class of the linked page. Here's a simplified example:

File 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()

File 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()

This then allows the script file to click on pages and get the object as a return value from that method, meaning the script author won't have to keep instantiating new pages as they navigate around the site. (I feel like this is an odd design, but I can't exactly put my finger on why, other than that it seems strange to have a method named 'clickSomeLink' and return an object of the resulting page.)

The following script illustrates how a script would navigate around the site: (I inserted print page to show how the page object changes)

Script File:

from homePageLib import HomePage

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

which produces the following output:

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

So, the part of this that I specifically feel most uneasy about are the from ____ import ____ lines that end up all over. These strike me as bad for the following reasons:

  1. I've always made it a convention to put all import statements at the top of a file.
  2. Since there may be multiple links to a page, this results in the same from foo import bar line of code in several places in a file.

The problem is, if we put these import statements at the top of the page, we get import errors, because (as per this example), HomePage imports CalendarPage and vice versa:

File 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()

File 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()

This results in the following error:

>>> 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

(tips on how to better format python output?)

Rather than perpetuating this style, I'd like to find a better way. Is there a Pythonic way to deal with circular dependencies like this and still keep import statements at the top of the file?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

泼猴你往哪里跑 2024-11-10 08:02:29

解析这些构造通常涉及诸如依赖注入之类的技术。

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

在calendarLib.py中:

import homePageLib

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

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

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

Resolving these constructs usually involves techniques like Dependency Injection.

It is, however, rather simple to fix this error:

In calendarLib.py:

import homePageLib

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

The code at module level is executed at import time. Using the from [...] import [...] syntax requires the module to be completely initialized to succeed.

A simple import [...] does not, because no symbols are accessed, thus breaking the dependency chain.

岁月如刀 2024-11-10 08:02:29

我有一个循环导入,因为我在类型提示中引用了一个类。
这可以使用 from __future__ import 注解来解决(使用 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

I have a circular import because I reference a class in a type hint.
This can be solved using from __future__ import annotations (tested with Python 3.9.x).

Example:

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
瘫痪情歌 2024-11-10 08:02:29

请阅读Sebastian 的回答以获取详细说明。这种方法是由 David Beazley 在 PyCon 中提出的。

尝试将导入定位在顶部,如下所示

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

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

Please read Sebastian's answer for detailed explanation. This approach was proposed by David Beazley in PyCon

Try positioning the imports on the top like this

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

This will try to import your HomePage and if failed, will try to load it from the cache

堇色安年 2024-11-10 08:02:29

这里尚未提及的另一种方法:将导入放在类定义下方。

# 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
        return CalendarPage()
        
from calendarLib import 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
        return HomePage()

from homePageLib import HomePage

适用于 Python 2 和 3。

Another method not yet mentioned here: put the imports below the class definitions.

# 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
        return CalendarPage()
        
from calendarLib import 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
        return HomePage()

from homePageLib import HomePage

Works in both Python 2 and 3.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文