V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
zxCoder
V2EX  ›  Python

Python 包导入的困惑

  •  
  •   zxCoder · 2021-11-05 08:52:33 +08:00 · 4392 次点击
    这是一个创建于 874 天前的主题,其中的信息可能已经有所发展或是发生改变。

    下载了一个 python 项目,目录是这样子的

    - XXXProject
      - A
        - __init__.py
        - xxx.py
        - yyy.py
        - ...
      - scripts
        - zzz.sh
    

    xxx.py 的内容是

    from A.yyy import YY
    if __name__ == "__main__":
        # .....
    

    yyy.py 的内容是

    class YYY():
        # .....
    

    zzz.sh 的内容是

    python A/xxx.py
    

    然后我就按照 README 所说,在项目根目录下执行 sh scripts/zzz.sh,结果报错了,错误是

    ModuleNotFoundError: No module named 'A'
    

    这是为什么呢?

    第 1 条附言  ·  2021-11-06 09:06:57 +08:00

    按大家的说法....这就是这个项目的README本身写错了?

    39 条回复    2021-11-08 14:00:36 +08:00
    mangoDB
        1
    mangoDB  
       2021-11-05 09:00:00 +08:00
    from yyy import YY
    Trim21
        2
    Trim21  
       2021-11-05 09:02:45 +08:00 via Android
    把 XXXProject 文件夹加到 python path 环境变量里
    Latin
        3
    Latin  
       2021-11-05 09:18:43 +08:00
    from __future__ import absolute_import
    from __future__ import division
    from __future__ import print_function

    import os.path as osp
    import sys


    def add_path(path):
    if path not in sys.path:
    sys.path.insert(0, path)


    this_dir = osp.dirname(__file__)

    lib_path = osp.join(this_dir, '..', 'lib')
    add_path(lib_path)
    ch2
        4
    ch2  
       2021-11-05 09:43:11 +08:00
    需要加 path
    misaka19000
        6
    misaka19000  
       2021-11-05 09:51:31 +08:00
    要把 XXXProject 这个目录加到 PythonPATH 里面去

    export PYTON_PATH=$PYTHON_PATH:/xxx/xxx/XXXProject
    sudoy
        7
    sudoy  
       2021-11-05 09:54:31 +08:00
    @mangoDB `from yyy import YY` +1 不需要`A.yyy`, 因为 xxx.pyyyy.py 在同一个文件夹下面
    Vegetable
        8
    Vegetable  
       2021-11-05 09:57:09 +08:00
    @misaka19000 根目录自动添加的吧
    misaka19000
        9
    misaka19000  
       2021-11-05 10:02:35 +08:00
    @Vegetable 谁给你自动添加? Python 怎么知道哪个目录是根目录
    Vegetable
        10
    Vegetable  
       2021-11-05 10:18:09 +08:00
    @misaka19000 python 的 sys.path 第一位永远是""
    Vegetable
        11
    Vegetable  
       2021-11-05 10:20:40 +08:00
    @frostming 你这个确实准
    misaka19000
        12
    misaka19000  
       2021-11-05 10:23:14 +08:00
    @Vegetable 有的时候是不会在项目的根路径执行程序的
    2i2Re2PLMaDnghL
        13
    2i2Re2PLMaDnghL  
       2021-11-05 11:08:32 +08:00
    我觉得更可能你没有安装这个项目,因为 git clone 下来的不会自动安装
    如果根目录下有 setup[.]py 的话,应先运行 pip install -e . (你可能会希望做一个 venv ,以免把 A 安装到系统里去)
    jaredyam
        14
    jaredyam  
       2021-11-05 11:10:39 +08:00
    我的观点,未验证:
    1. zzz.sh 的执行路径(在根目录)和执行方式( python A/xxx.py )没有问题
    2. 问题是此时 A/xxx.py 被看作为为一个脚本文件直接执行,却使用了 from A.yyy import YY 从它外部的一个包(其实是包括自己的包,但包不包括自己不重要)导入模块 yyy ,按理来说直接 from yyy import YY 就好了,不需要考虑 A ,直接把 yyy 作为同级模块导入即可
    3. 但是按照 2 的解决思路,A 的结构就显得很怪异,A 被强行看作一个包(有__init__.py ),但却又包含脚本。一般实践中,xxx.p 应该被拿出来,放在包外面,比如和 scripts/同级,然后在 zzz.sh 中 python xxx.py ,具体放哪看脚本干的事情,但放包里就是有问题
    2i2Re2PLMaDnghL
        15
    2i2Re2PLMaDnghL  
       2021-11-05 11:11:25 +08:00
    @2i2Re2PLMaDnghL 发现排版不太好看清,
    ```
    pip install -e .
    ```
    最后的 dot 是必要的。
    2i2Re2PLMaDnghL
        16
    2i2Re2PLMaDnghL  
       2021-11-05 11:15:13 +08:00
    @jaredyam 确实挺怪的,不过一般实践应该是为 A.xxx:main 设置一个 entrypoint ,或者 python -m A.xxx
    xingheng
        17
    xingheng  
       2021-11-05 14:33:57 +08:00
    我认为 sys.path 都是扯犊子,我从来不那么干,除非在写加载动态化插件。Python 会把当前工作目录加入到 sys.path 里,这一点儿就够了。

    直接改写 zzz.sh:
    python ../A/xxx.py
    akaHenry
        18
    akaHenry  
       2021-11-05 16:49:34 +08:00
    @Latin sys.path.insert() 这种很脏的做法, 就不要再传播了.


    Python 是通过 __init__.py 文件来区分 普通文件夹和 package 目录的.


    你 XXXProject/ 目录下, 并没有__init__.py 文件, 你的脚本, 并不会认为这是 Python 项目.


    很久不写 py 了. 建议你添加个 __init__.py 文件在 XXXProject/ 目录, 再试一下.


    Python 的项目路径环境, 没什么复杂的.
    akaHenry
        19
    akaHenry  
       2021-11-05 16:51:23 +08:00
    @misaka19000 Python 社区, 都凋零成这样了吗?

    一个小脚本, 都开始教他添加环境变量了?

    阿西吧.
    akaHenry
        20
    akaHenry  
       2021-11-05 16:54:50 +08:00
    @xingheng 你的回复, 还算正常. ls 的都是什么鬼.

    你的回复, 配合我上面的建议的修改. 应该就正常了.
    zxCoder
        21
    zxCoder  
    OP
       2021-11-05 17:02:06 +08:00
    @orzglory 我用一楼的方法改好了。。。。但是谁能告诉我这是什么原因,python 版本问题吗?

    这个项目我看也没有人提出相关的 issue ,肯定是能跑的
    Latin
        22
    Latin  
       2021-11-05 17:07:52 +08:00
    Latin
        23
    Latin  
       2021-11-05 17:08:32 +08:00
    基本上开源出来的算法模块现在都是这个套路吼
    misaka19000
        24
    misaka19000  
       2021-11-05 17:26:54 +08:00   ❤️ 2
    @orzglory 我就教了怎么了,你哪来的优越感?下次怼人之前麻烦先把你写的代码亮出来看看都是些什么东西
    oOoOoOoOoOo
        25
    oOoOoOoOoOo  
       2021-11-05 20:40:41 +08:00 via Android
    working_dir 决定一切
    manfredexz
        26
    manfredexz  
       2021-11-05 21:05:24 +08:00
    当你搞不清楚谁说的是对的时候,请查阅官方的文档
    https://docs.python.org/3/tutorial/modules.html#the-module-search-path

    简单来说先查内置 package 再查脚本的当前目录 再查 PYTHONPATH
    ila
        27
    ila  
       2021-11-05 21:28:13 +08:00 via Android
    你去看 flask 等项目的目录结构,
    被 import 的路径里包含了__init__.py 文件。
    而且官方文档也是建议这样做。
    ila
        28
    ila  
       2021-11-05 21:29:25 +08:00 via Android
    不要第一步就改环境变量,该设置的都在安装(软件和包)时设置好了
    XIVN1987
        29
    XIVN1987  
       2021-11-05 21:48:25 +08:00
    PEP 328: all import statements be absolute by default (searching sys.path only) with special syntax (leading dots) for accessing package-relative imports.
    “from A.yyy import YY” 显然是个绝对导入,所以 python 肯定是在 sys.path 中的路径中查找 A 。。那程序要想执行只有两种方法:
    1 、把 XXXProject 的路径加入 sys.path
    2 、把 A 安装到现有的 sys.path 中去,比如 “lib/site-packages” 目录下
    XIVN1987
        30
    XIVN1987  
       2021-11-05 22:04:50 +08:00
    ```
    >>> import sys
    >>> for x in sys.path: print(repr(x).replace(r'\\', '/'))
    ''
    'C:/Python36/python36.zip'
    'C:/Python36/DLLs'
    'C:/Python36/lib'
    'C:/Python36'
    'C:/Python36/lib/site-packages'
    ```

    当前目录确实在 sys.path 中,但当前目录指的是被 python.exe 直接执行的那个 xxx.py 所在的目录,所以 XXXProject/A 的路径在 sys.path 中,但 XXXProject 不在 sys.path 中。
    所以题主的“from A.yyy import YY”执行出错,而一楼的“from yyy import YY”执行正常。
    Trim21
        31
    Trim21  
       2021-11-05 22:32:26 +08:00 via Android
    @Latin 你没注意到这几行实际上被注释掉了吗…
    akaHenry
        32
    akaHenry  
       2021-11-08 12:38:20 +08:00
    @zxCoder 我的解释是原因, 你按照我的改法, 添加一个 init.py 文件(补上面的下划线, v 站说这是链接). 应该不用改代码就可以跑. 原因, 我解释了哇.

    from A.yyy , 这里 A 是 package 的前提是, A 同级目录, 要有 init.py 文件. 才是 Python package 目录, 否则只是普通文件夹.
    akaHenry
        33
    akaHenry  
       2021-11-08 12:40:56 +08:00
    @Trim21 所以这些人, 真的可爱. 贴个注释给我看.

    27 楼, 跟我指出的问题. 是一致的.

    这些不懂装懂的, 就不要出来误导别人.

    这下面, 一排连 Python package 模块目录和 普通目录都没搞清楚的, 这基本功, 够可以.
    akaHenry
        34
    akaHenry  
       2021-11-08 12:46:55 +08:00
    @misaka19000 不懂装懂, 指出来. 就跳脚.

    py 社区, 就剩下这种水平的出来胡说八道. 可悲.
    Trim21
        35
    Trim21  
       2021-11-08 12:48:58 +08:00
    @orzglory 没有__init__.py 文件的还有可能是 python namespace package 啊...
    akaHenry
        36
    akaHenry  
       2021-11-08 12:51:29 +08:00
    @Trim21 lz 的这个脚本. 显然与此无关呀.
    akaHenry
        37
    akaHenry  
       2021-11-08 12:55:49 +08:00
    namespace package 是给 setup.py 这种安装脚本用的. 不是给日常场景滥用的.

    这个特性本身, 我认为只是为了 hack 一些问题而打的补丁.
    onlylovehuan
        38
    onlylovehuan  
       2021-11-08 13:27:35 +08:00
    跑的时候命令行先跑到 XXXProject 路径下面,再执行
    O5oz6z3
        39
    O5oz6z3  
       2021-11-08 14:00:36 +08:00
    #5 和#26 楼的文档说完了。项目可能没问题如#13 #29 所说需要全局安装。
    #26 docs.python.org/zh-cn/3/tutorial/modules.html#the-module-search-path
    #12 docs.python.org/zh-cn/3/using/cmdline.html#cmdarg-script
    #6 docs.python.org/zh-cn/3/using/cmdline.html#envvar-PYTHONPATH
    #16 docs.python.org/zh-cn/3/using/cmdline.html#cmdoption-m
    __init__.py 只是规范问题,加不加效果上我猜区别不大……
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3343 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 39ms · UTC 13:42 · PVG 21:42 · LAX 06:42 · JFK 09:42
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.