【问题标题】:python paths and import orderpython路径和导入顺序
【发布时间】:2012-03-06 15:28:04
【问题描述】:

我真的很想解决这个问题,因为我在生成一些大的 py2app/py2exe 包时经常遇到它。我的包包含许多模块/包,这些模块/包也可能位于用户站点包/默认位置(如果用户有 python 分发版),但我希望我的分发包在从我的分发版运行时在它们之前生效。

现在从我读到的here PYTHONPATH 应该是在当前目录之后添加到sys.path 的第一件事,但是从我在我的机器上测试的情况来看并非如此,所有文件夹$site-packages$/easy-install.pth 中定义的优先级高于此。

谁能给我一些关于这个导入顺序的更深入的解释,并帮助我找到一种方法来设置环境变量,使我分发的包优先于默认安装的包?

到目前为止,我的尝试是,例如在 Mac-OS py2app 上,在我的入口点脚本中:

 os.environ['PYTHONPATH'] = DATA_PATH + ':'
 os.environ['PYTHONPATH'] = os.environ['PYTHONPATH'] + os.path.join(DATA_PATH
                                                            , 'lib') + ':'
 os.environ['PYTHONPATH'] = os.environ['PYTHONPATH'] + os.path.join(
                                DATA_PATH, 'lib', 'python2.7', 'site-packages') + ':'
 os.environ['PYTHONPATH'] = os.environ['PYTHONPATH'] + os.path.join(
                          DATA_PATH, 'lib', 'python2.7', 'site-packages.zip')

这基本上是py2app生成的包的结构。然后我就:

 SERVER = subprocess.Popen([PYTHON_EXE_PATH, '-m', 'bin.rpserver'
                            , cfg.RPC_SERVER_IP, cfg.RPC_SERVER_PORT],
                            shell=False, stdin=IN_FILE, stdout=OUT_FILE, 
                            stderr=ERR_FILE)

这里PYTHON_EXE_PATH是py2app添加到包中的python可执行文件的路径。这在没有安装 python 的机器上运行良好。但是,当 python 分发已经存在时,它的站点包优先。

【问题讨论】:

    标签: python


    【解决方案1】:

    Python 按顺序搜索sys.path 中的路径(参见http://docs.python.org/tutorial/modules.html#the-module-search-path)。 easy_install 直接更改此列表(请参阅您的 easy-install.pth 文件中的最后一行):

    import sys; new=sys.path[sys.__plen:]; del sys.path[sys.__plen:]; p=getattr(sys,'__egginsert',0); sys.path[p:p]=new; sys.__egginsert = p+len(new)
    

    这基本上接受添加的任何目录并将它们插入列表的开头。

    另见Eggs in path before PYTHONPATH environment variable

    【讨论】:

    • 在分发 py2exe 项目时可能最简单的选择是分发所有必要的模块和 pythonXX.dll蟒蛇。另一个有趣的项目可能是 virtualenv (pypi.python.org/pypi/virtualenv)
    • 所以分发 pythonXX.dll 应该会停止我刚才描述的过程吗? .dll 应该在分发文件夹的哪个级别?我问是因为目前我正在将 pythonXX.dll 添加到包中,但问题仍然存在。
    【解决方案2】:

    此页面是“Python 导入顺序”的高 Google 搜索结果,所以这里有一个希望更清晰的解释:

    正如这两页所解释的,import 的顺序是:

    1. 内置 python 模块。您可以在变量sys.modules 中看到列表。
    2. sys.path 条目。
    3. 依赖于安装的默认位置。

    正如sys.path 文档页面所解释的,它的填充如下:

    1. 第一个条目是指向python 开始的文件目录的完整路径(所以/someplace/on/disk/> $ python /path/to/the/run.py 表示第一个路径是/path/to/the/,同样,如果你在/path/to/> $ python the/run.py (无论你给 python 一个相对文件还是绝对文件,它仍然总是设置为目录的完整路径),或者如果 python 是在没有文件的情况下启动的,也就是交互模式,它将是一个空字符串(空字符串表示“python 进程的当前工作目录”)。换句话说,Python 假定您启动的文件希望能够对与您启动 python 的文件位于同一位置的 package/-foldersblah.py 模块进行相对导入。
    2. sys.path 中的其他条目由PYTHONPATH 环境变量填充。基本上是安装了第三方 python 包的全局 pip 文件夹(例如 requestsnumpytensorflow)。

    所以,基本上:是的,您可以相信 Python 会在任何全局安装的 pip 内容之前首先找到您的本地包文件夹和模块文件。

    下面是一个进一步解释的例子:

    myproject/ # <-- This is not a package (no __init__.py file).
      modules/ # <-- This is a package (has an __init__.py file).
        __init__.py
        foo.py
      run.py
      second.py
    
    executed with: python /path/to/the/myproject/run.py
    will cause sys.path[0] to be "/path/to/the/myproject/"
    
    run.py contents:
    import modules.foo as foo # will import "/path/to/the/myproject/" + "modules/foo.py"
    import second # will import "/path/to/the/myproject/" + "second.py"
    
    second.py contents:
    import modules.foo as foo # will import "/path/to/the/myproject/" + "modules/foo.py"
    

    编辑:

    您可以运行以下命令来打印所有内置模块名称的排序列表。这些是在项目中的任何自定义文件/模块文件夹之前加载的内容。基本上这些是您在自己的自定义文件中必须避免使用的名称:

    python -c "import sys, json; print(json.dumps(sorted(list(sys.modules.keys())), indent=4))"

    从 Python 3.9.0 开始的列表:

    "__main__",
    "_abc",
    "_bootlocale",
    "_codecs",
    "_collections",
    "_collections_abc",
    "_frozen_importlib",
    "_frozen_importlib_external",
    "_functools",
    "_heapq",
    "_imp",
    "_io",
    "_json",
    "_locale",
    "_operator",
    "_signal",
    "_sitebuiltins",
    "_sre",
    "_stat",
    "_thread",
    "_warnings",
    "_weakref",
    "abc",
    "builtins",
    "codecs",
    "collections",
    "copyreg",
    "encodings",
    "encodings.aliases",
    "encodings.cp1252",
    "encodings.latin_1",
    "encodings.utf_8",
    "enum",
    "functools",
    "genericpath",
    "heapq",
    "io",
    "itertools",
    "json",
    "json.decoder",
    "json.encoder",
    "json.scanner",
    "keyword",
    "marshal",
    "nt",
    "ntpath",
    "operator",
    "os",
    "os.path",
    "pywin32_bootstrap",
    "re",
    "reprlib",
    "site",
    "sre_compile",
    "sre_constants",
    "sre_parse",
    "stat",
    "sys",
    "time",
    "types",
    "winreg",
    "zipimport"
    

    因此,切勿为您的 .py 文件或您的项目模块子文件夹使用任何这些名称。

    【讨论】:

      【解决方案3】:

      导入模块后,python首先从sys.modules目录列表中搜索。 如果未找到,则从sys.path 目录列表中搜索。您的操作系统上可能还有其他 python 搜索列表

      import time , sys
      print (sys.modules)
      print (sys.path)
      

      输出是目录列表:

      {... , ... , .....}
      ['C:\\Users\\****', 'C:\\****', ....']
      

      time模块按照sys.modulessys.path列表的顺序导入。

      【讨论】:

      • 感谢您的帮助。我只是想补充一点,如果您要导入的模块与 sys.modules 列表中的模块同名,则必须删除模块条目才能使用您自己的模块。这就是我所做的:del sys.modules["your module's name here"]
      【解决方案4】:

      尽管上述关于解释器扫描sys.path 的顺序的答案是正确的,但优先于例如如果PYTHONPATH 变量中没有完整的用户路径,则site-packages 部署包上的用户文件路径可能会失败。

      例如,假设您有以下命名空间包结构:

      /opt/repo_root
        - project  # this is the base package that brigns structure to the namespace hierarchy
        - my_pkg
        - my_pkg-core
        - my_pkg-gui
        - my_pkg-helpers
        - my_pkg-helpers-time_sync
      

      上述包都具有内部所需的结构和元数据,以便可以由 conda 部署,并且这些也都已安装。因此,我可以打开一个 python shell 并输入:

      >>> from project.my_pkg.helpers import time_sync
      >>> print(time_sync.__file__)
      
      /python/interpreter/path/lib/python3.6/site_packages/project/my_pkg/helpers/time_sync/__init__.py
      

      将在 python 解释器的 site-packages 子文件夹中返回一些路径。如果我手动将要导入的包添加到PYTHONPATH 甚至sys.path,什么都不会改变。

      >>> import os
      
      >>> # joining separator ":" for Unix, ";" for NT
      >>> os.environ['PYTHONPATH'] = ":".join(os.environ['PYTHONPATH'], "/opt/repo_root/my_pkg-helpers-time_sync")
      
      >>> from project.my_pkg.helpers import time_sync
      >>> print(time_sync.__file__)
      
      /python/interpreter/path/lib/python3.6/site_packages/project/my_pkg/helpers/time_sync/__init__.py
      

      仍然返回包已从site-packages 导入。您需要将整个路径层次结构包含到PYTHONPATH 中,就好像它是一个传统的python 包一样,然后它就会按您的预期工作:

      >>> import os
      
      >>> # joining separator ":" for Unix, ";" for NT
      >>> os.environ['PYTHONPATH'] = ":".join(
      ... os.environ['PYTHONPATH'],
      ... "/opt/repo_root",
      ... "/opt/repo_root/project",
      ... "/opt/repo_root/project/my_pkg",
      ... "/opt/repo_root/project/my_pkg-helpers",
      ... "/opt/repo_root/project/my_pkg-helpers-time_sync"
      ... )
      
      >>> from project.my_pkg.helpers import time_sync
      >>> print(time_sync.__file__)
      
      /opt/project/my_pkg/helpers/time_sync/__init__.py
      

      【讨论】:

        猜你喜欢
        • 2013-04-16
        • 1970-01-01
        • 1970-01-01
        • 2015-01-07
        • 1970-01-01
        • 1970-01-01
        • 2020-04-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多