解决循环导入语句的Pythonic方式?
我最近接手了一些代码,感觉有点不安:这里有一个测试库,里面有很多类,每个类对应我们网站上的一个网页,每个网页类都有一些方法来自动化该页面的功能。
这些方法可以用来点击页面之间的链接,并返回链接页面的类。下面是一个简化的例子:
文件 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 ____
语句。我觉得这样不好,原因有以下几点:
- 我一直有个习惯,就是把所有的导入语句放在文件的顶部。
- 由于可能有多个链接指向同一个页面,这样就会导致在文件的多个地方出现相同的
from foo import bar
代码行。
问题是,如果我们把这些导入语句放在页面顶部,就会出现导入错误,因为(根据这个例子),HomePage导入了CalendarPage,而CalendarPage又反过来导入了HomePage:
文件 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的方式来处理这样的循环依赖,同时还能把导入语句放在文件的顶部?
4 个回答
请查看Sebastian的回答,里面有详细的解释。这种方法是David Beazley在PyCon上提出的。
试着把导入的部分放在最上面,像这样:
try:
from homePageLib import HomePage
except ImportError:
import sys
HomePage = sys.modules[__package__ + '.HomePage']
这样做会尝试导入你的HomePage
,如果导入失败,它会尝试从缓存中加载。
我遇到了一个循环导入的问题,因为我在类型提示中引用了一个类。这个问题可以通过使用 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
解决这些问题通常需要用到一些技术,比如依赖注入。
不过,修复这个错误其实很简单:
在calendarLib.py文件中:
import homePageLib
class CalendarPage(object):
def clickHomePageLink(self):
[...]
return homePageLib.HomePage()
模块级别的代码在导入时就会执行。使用from [...] import [...]
这种语法时,模块必须完全初始化才能成功。
而简单的import [...]
就不需要这样,因为没有访问任何符号,这样就打破了依赖关系。