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
    时,如本节所述