我有一个包含子软件包的软件包,在运行时仅需要导入其中一个子软件包-但我需要测试它们是否有效。这是我的文件夹结构:
game/
__init__.py
game1/
__init__.py
constants.py
...
game2/
__init__.py
constants.py
...
现在,在启动时运行的代码可以:
import pkgutil
import game as _game
# Detect the known games
for importer,modname,ispkg in pkgutil.iter_modules(_game.__path__):
if not ispkg: continue # game support modules are packages
# Equivalent of "from game import <modname>"
try:
module = __import__('game',globals(),locals(),[modname],-1)
except ImportError:
deprint(u'Error in game support module:', modname, traceback=True)
continue
submod = getattr(module,modname)
if not hasattr(submod,'fsName') or not hasattr(submod,'exe'): continue
_allGames[submod.fsName.lower()] = submod
但这有一个缺点,即所有子包都被导入,从而导入了子包中的其他模块(例如constants.py等),这相当于几兆字节的垃圾。因此,我想用一个子模块有效的测试来代替此代码(它们会很好地导入)。我想我应该以某种方式使用eval-但是如何?或者我该怎么办?
编辑: tldr;
我正在寻找等效于上述循环的核心:
try:
probaly_eval(game, modname) # fails iff `from game import modname` fails
# but does _not_ import the module
except: # I'd rather have a more specific error here but methinks not possible
deprint(u'Error in game support module:', modname, traceback=True)
continue
所以我想要一个明确的答案,如果存在与导入语句完全相同的可视化错误检查-不导入模块。那是我的问题,很多回答者和评论者回答了不同的问题。
我们已经有一个自定义的导入器(免责声明:我没有写代码,我只是当前的维护者),其load_module
:
def load_module(self,fullname):
if fullname in sys.modules:
return sys.modules[fullname]
else: # set to avoid reimporting recursively
sys.modules[fullname] = imp.new_module(fullname)
if isinstance(fullname,unicode):
filename = fullname.replace(u'.',u'\\')
ext = u'.py'
initfile = u'__init__'
else:
filename = fullname.replace('.','\\')
ext = '.py'
initfile = '__init__'
try:
if os.path.exists(filename+ext):
with open(filename+ext,'U') as fp:
mod = imp.load_source(fullname,filename+ext,fp)
sys.modules[fullname] = mod
mod.__loader__ = self
else:
mod = sys.modules[fullname]
mod.__loader__ = self
mod.__file__ = os.path.join(os.getcwd(),filename)
mod.__path__ = [filename]
#init file
initfile = os.path.join(filename,initfile+ext)
if os.path.exists(initfile):
with open(initfile,'U') as fp:
code = fp.read()
exec compile(code, initfile, 'exec') in mod.__dict__
return mod
except Exception as e: # wrap in ImportError a la python2 - will keep
# the original traceback even if import errors nest
print 'fail', filename+ext
raise ImportError, u'caused by ' + repr(e), sys.exc_info()[2]
所以我想我可以sys.modules
用可覆盖的方法替换访问缓存的部分,这些方法在我的覆盖中将单独保留该缓存:
所以:
@@ -48,2 +55,2 @@ class UnicodeImporter(object):
- if fullname in sys.modules:
- return sys.modules[fullname]
+ if self._check_imported(fullname):
+ return self._get_imported(fullname)
@@ -51 +58 @@ class UnicodeImporter(object):
- sys.modules[fullname] = imp.new_module(fullname)
+ self._add_to_imported(fullname, imp.new_module(fullname))
@@ -64 +71 @@ class UnicodeImporter(object):
- sys.modules[fullname] = mod
+ self._add_to_imported(fullname, mod)
@@ -67 +74 @@ class UnicodeImporter(object):
- mod = sys.modules[fullname]
+ mod = self._get_imported(fullname)
并定义:
class FakeUnicodeImporter(UnicodeImporter):
_modules_to_discard = {}
def _check_imported(self, fullname):
return fullname in sys.modules or fullname in self._modules_to_discard
def _get_imported(self, fullname):
try:
return sys.modules[fullname]
except KeyError:
return self._modules_to_discard[fullname]
def _add_to_imported(self, fullname, mod):
self._modules_to_discard[fullname] = mod
@classmethod
def cleanup(cls):
cls._modules_to_discard.clear()
然后我在sys.meta_path中添加了导入器,很高兴:
importer = sys.meta_path[0]
try:
if not hasattr(sys,'frozen'):
sys.meta_path = [fake_importer()]
perform_the_imports() # see question
finally:
fake_importer.cleanup()
sys.meta_path = [importer]
对 ?错误!
Traceback (most recent call last):
File "bash\bush.py", line 74, in __supportedGames
module = __import__('game',globals(),locals(),[modname],-1)
File "Wrye Bash Launcher.pyw", line 83, in load_module
exec compile(code, initfile, 'exec') in mod.__dict__
File "bash\game\game1\__init__.py", line 29, in <module>
from .constants import *
ImportError: caused by SystemError("Parent module 'bash.game.game1' not loaded, cannot perform relative import",)
Hu?我当前正在导入非常相同的模块。好吧,答案可能在导入文档中
如果在高速缓存中未找到模块,则搜索sys.meta_path(可以在PEP 302中找到sys.meta_path的规范)。
这还不完全是重点,但我想是该语句from .constants import *
查找sys.modules来检查父模块是否存在,并且我看不到任何绕过该方法的方式(请注意,我们的自定义加载器正在使用内置的导入机制模块,mod.__loader__ = self
是在事实之后设置的)。
因此,我更新了FakeImporter以使用sys.modules缓存,然后进行清理。
class FakeUnicodeImporter(UnicodeImporter):
_modules_to_discard = set()
def _check_imported(self, fullname):
return fullname in sys.modules or fullname in self._modules_to_discard
def _add_to_imported(self, fullname, mod):
super(FakeUnicodeImporter, self)._add_to_imported(fullname, mod)
self._modules_to_discard.add(fullname)
@classmethod
def cleanup(cls):
for m in cls._modules_to_discard: del sys.modules[m]
但是,这以一种新的方式-或两种方式产生了爆炸:
对游戏/程序bash
包的引用保存在sys.modules的顶级程序包实例中:
bash\
__init__.py
the_code_in_question_is_here.py
game\
...
因为game
被导入为bash.game
。该引用包含对所有game1, game2,...
子包的引用,因此这些子包绝不会被垃圾回收
bash.brec
与同一bash
模块实例一样。该引用是from .. import brec
在game \ game1中导入的,而不会触发import来更新SomeClass
。但是,在另一个模块中,表单的导入from ...brec import SomeClass
确实触发了导入,并且brec模块的另一个实例最终出现在sys.modules中。该实例未更新,SomeClass
并因AttributeError而自爆。两者都是通过手动删除这些引用来解决的-因此,gc收集了所有模块(75个内存中有5 MB内存),并且from .. import brec
确实触发了导入(这from ... import foo
与from ...foo import bar
问题有关)。
这个故事的寓意是有可能但:
如果这听起来很复杂且容易出错,那么-至少现在我对相互依赖性及其危险性有更清晰的认识-是时候解决这个问题了。
这篇文章是由Pydev的调试器赞助的-我发现该gc
模块对于从这里了解正在发生的事情非常有用。当然,有很多变量是调试器的变量,也很复杂
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句