Python3.6项目结构导致运行时警告
我试图打包我的项目以供发布,但在运行模块时遇到了Python3.6项目结构导致运行时警告,python,python-3.x,Python,Python 3.x,我试图打包我的项目以供发布,但在运行模块时遇到了RuntimeWarning 我在上发现了一个bug报告,它表明RuntimeWarning是Python 3.5.2中引入的新行为 通过阅读bug报告,似乎发生了双重导入,并且此运行时警告在警告用户时是正确的。然而,我不知道我需要对自己的项目结构做什么更改来避免这个问题 这是我试图“正确”构建的第一个项目。我想有一个整洁的布局,当我推的代码,和一个项目结构,可以被克隆和运行容易的其他人 我的结构主要基于 我在下面添加了一个最小工作示例的详细信息
RuntimeWarning
我在上发现了一个bug报告,它表明RuntimeWarning
是Python 3.5.2中引入的新行为
通过阅读bug报告,似乎发生了双重导入,并且此运行时警告在警告用户时是正确的。然而,我不知道我需要对自己的项目结构做什么更改来避免这个问题
这是我试图“正确”构建的第一个项目。我想有一个整洁的布局,当我推的代码,和一个项目结构,可以被克隆和运行容易的其他人
我的结构主要基于
我在下面添加了一个最小工作示例的详细信息
为了复制这个问题,我使用python-m
运行主文件:
(py36) X:\test_proj>python -m proj.proj
C:\Users\Matthew\Anaconda\envs\py36\lib\runpy.py:125: RuntimeWarning:
'proj.proj' found in sys.modules after import of package 'proj', but prior
to execution of 'proj.proj'; this may result in unpredictable behaviour
warn(RuntimeWarning(msg))
This is a test project.`
运行我的测试很好:
(py36) X:\test_proj>python -m unittest tests.test_proj
This is a test project.
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
复制该问题的项目结构如下:
myproject/
proj/
__init__.py
proj.py
tests/
__init__.py
context.py
test_proj.py
import unittest
from proj import proj
在文件proj/proj.py
中:
def main():
print('This is a test project.')
raise ValueError
if __name__ == '__main__':
main()
from .proj import main
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import proj
import unittest
from .context import proj
class SampleTestCase(unittest.TestCase):
"""Test case for this sample project"""
def test_raise_error(self):
"""Test that we correctly raise an error."""
with self.assertRaises(ValueError):
proj.main()
if __name__ == '__main__':
unittest.main()
from .proj import main
在proj/\uuuuu init\uuuuuuu.py
中:
def main():
print('This is a test project.')
raise ValueError
if __name__ == '__main__':
main()
from .proj import main
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import proj
import unittest
from .context import proj
class SampleTestCase(unittest.TestCase):
"""Test case for this sample project"""
def test_raise_error(self):
"""Test that we correctly raise an error."""
with self.assertRaises(ValueError):
proj.main()
if __name__ == '__main__':
unittest.main()
from .proj import main
在测试/context.py
中:
def main():
print('This is a test project.')
raise ValueError
if __name__ == '__main__':
main()
from .proj import main
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import proj
import unittest
from .context import proj
class SampleTestCase(unittest.TestCase):
"""Test case for this sample project"""
def test_raise_error(self):
"""Test that we correctly raise an error."""
with self.assertRaises(ValueError):
proj.main()
if __name__ == '__main__':
unittest.main()
from .proj import main
最后,在测试/test_proj.py
中:
def main():
print('This is a test project.')
raise ValueError
if __name__ == '__main__':
main()
from .proj import main
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import proj
import unittest
from .context import proj
class SampleTestCase(unittest.TestCase):
"""Test case for this sample project"""
def test_raise_error(self):
"""Test that we correctly raise an error."""
with self.assertRaises(ValueError):
proj.main()
if __name__ == '__main__':
unittest.main()
from .proj import main
有人能帮我纠正我的项目结构以避免这种双重导入的情况吗?如果您有任何帮助,我们将不胜感激。如果您查看以下内容:
下一个陷阱存在于Python的所有当前版本中,包括
可以总结为以下一般准则:“切勿直接添加程序包目录或程序包内的任何目录
到Python路径“
出现问题的原因是该目录中的每个模块
现在可以使用两个不同的名称访问:作为顶部
级别模块(因为目录位于sys.path上)并作为子模块
包的目录(如果包含包的更高级别目录
本身也在sys.path上)
在tests/context.py中
删除:sys.path.insert(0,os.path.abspath(os.path.join(os.path.dirname(_u文件,“…”))
这可能会导致问题,而您的代码仍能按预期工作
因评论而编辑:
您可以尝试更改代码中的某些部分:
proj/\uuuu init\uuuuu.py
可以完全为空
在test_proj.py
上,应按如下方式更改导入:
myproject/
proj/
__init__.py
proj.py
tests/
__init__.py
context.py
test_proj.py
import unittest
from proj import proj
PS:我无法用您的初始代码或我的建议在Linux上重现警告。对于这种特殊情况,双重导入警告是由于proj/\uuu init\uuuuuuuuuuy.py
中的这一行引起的:
def main():
print('This is a test project.')
raise ValueError
if __name__ == '__main__':
main()
from .proj import main
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import proj
import unittest
from .context import proj
class SampleTestCase(unittest.TestCase):
"""Test case for this sample project"""
def test_raise_error(self):
"""Test that we correctly raise an error."""
with self.assertRaises(ValueError):
proj.main()
if __name__ == '__main__':
unittest.main()
from .proj import main
该行的意思是,当-m
开关实现完成导入项目
步骤时,项目项目
已经作为导入父包的副作用导入
避免警告
为了避免警告,您需要找到一种方法来确保导入父包不会隐式导入使用-m
开关执行的包
解决此问题的两个主要选项是:
从.proj import main
行中删除(如@John Moutafis所建议的),假设这样做不会破坏API兼容性保证;或
从proj
子模块中删除if
if
name\uuuu==“\uuuuuuu main\uuuu”:
块,并将其替换为一个单独的proj/\uuuu main\uuuuu.py
文件,该文件仅用于:
from .proj import main
main()
如果使用选项2,那么命令行调用也将更改为仅为python-m proj
,而不是引用子模块
选项2的一个更向后兼容的变体是添加\uuuu main\uuuuuu.py
,而不从当前子模块中删除CLI块,这在与弃用警告
结合使用时是一种特别好的方法:
if __name__ == "__main__":
import warnings
warnings.warn("use 'python -m proj', not 'python -m proj.proj'", DeprecationWarning)
main()
如果proj/\uuuuu main\uuuuu.py
已经被用于其他目的,那么您也可以用python-m proj.proj.cli
替换python-m proj.proj.py
,其中proj/proj.py
如下所示:
if __name__ != "__main__":
raise RuntimeError("Only for use with the -m switch, not as a Python API")
from .proj import main
main()
为什么会出现警告?
当-m
开关实现即将在\uuuuu main\uuuuu
模块中再次运行已导入的模块代码时,会发出此警告,这意味着您将拥有它定义的所有内容的两个不同副本-类、函数、容器等
根据应用程序的具体情况,这可能工作正常(这就是为什么它是一个警告而不是一个错误),或者它可能导致奇怪的行为,如模块级状态修改未按预期共享,甚至由于异常处理程序试图从模块的一个实例捕获异常类型,而引发的异常使用了另一个实例的类型,因此无法捕获异常
因此,模糊的可能会导致不可预测的行为
警告-如果由于两次运行模块的顶级代码而出现问题,那么症状可能几乎是任何东西
如何调试更复杂的情况?
在这个特定的示例中,副作用导入直接在proj/\uuuuu init\uuuuuuuy.py
中,有一个更微妙、更难调试的变体,而父包却执行以下操作:
import some_other_module
然后是某个其他模块(或它导入的模块)执行以下操作:
import proj.proj # or "from proj import proj"
假设错误行为是可复制的,调试此类问题的主要方法是在详细模式下运行python并检查导入顺序:
$ python -v -c "print('Hello')" 2>&1 | grep '^import'
import zipimport # builtin
import site # precompiled from /usr/lib64/python2.7/site.pyc
import os # precompiled from /usr/lib64/python2.7/os.pyc
import errno # builtin
import posix # builtin
import posixpath # precompiled from /usr/lib64/python2.7/posixpath.pyc
import stat # precompiled from /usr/lib64/python2.7/stat.pyc
import genericpath # precompiled from /usr/lib64/python2.7/genericpath.pyc
import warnings # precompiled from /usr/lib64/python2.7/warnings.pyc
import linecache # precompiled from /usr/lib64/python2.7/linecache.pyc
import types # precompiled from /usr/lib64/python2.7/types.pyc
import UserDict # precompiled from /usr/lib64/python2.7/UserDict.pyc
import _abcoll # precompiled from /usr/lib64/python2.7/_abcoll.pyc
import abc # precompiled from /usr/lib64/python2.7/abc.pyc
import _weakrefset # precompiled from /usr/lib64/python2.7/_weakrefset.pyc
import _weakref # builtin
import copy_reg # precompiled from /usr/lib64/python2.7/copy_reg.pyc
import traceback # precompiled from /usr/lib64/python2.7/traceback.pyc
import sysconfig # precompiled from /usr/lib64/python2.7/sysconfig.pyc
import re # precompiled from /usr/lib64/python2.7/re.pyc
import sre_compile # precompiled from /usr/lib64/python2.7/sre_compile.pyc
import _sre # builtin
import sre_parse # precompiled from /usr/lib64/python2.7/sre_parse.pyc
import sre_constants # precompiled from /usr/lib64/python2.7/sre_constants.pyc
import _locale # dynamically loaded from /usr/lib64/python2.7/lib-dynload/_localemodule.so
import _sysconfigdata # precompiled from /usr/lib64/python2.7/_sysconfigdata.pyc
import abrt_exception_handler # precompiled from /usr/lib64/python2.7/site-packages/abrt_exception_handler.pyc
import encodings # directory /usr/lib64/python2.7/encodings
import encodings # precompiled from /usr/lib64/python2.7/encodings/__init__.pyc
import codecs # precompiled from /usr/lib64/python2.7/codecs.pyc
import _codecs # builtin
import encodings.aliases # precompiled from /usr/lib64/python2.7/encodings/aliases.pyc
import encodings.utf_8 # precompiled from /usr/lib64/python2.7/encodings/utf_8.pyc
这个特定的示例仅显示了Fedora上的Python2.7在启动时执行的基本导入集。调试双重导入RuntimeWarning
时,如本节所述