在Python中从相对路径导入

157

我有一个用于客户端代码的文件夹,一个用于服务器端代码的文件夹,以及一个用于在它们之间共享的代码的文件夹

Proj/
    Client/
        Client.py
    Server/
        Server.py
    Common/
        __init__.py
        Common.py

如何从Server.py和Client.py中导入Common.py?


相关链接:https://dev59.com/y3VD5IYBdhLWcg3wJIAK - moooeeeep
7个回答

216

2014年11月更新(3年后):

Python 2.6和3.x支持正确的相对导入,您可以避免使用任何hacky方法。 使用此方法,您知道您正在获取一个相对导入而不是绝对导入。 '..' 的意思是,进入上一级目录:

from ..Common import Common

需要注意的是,这个方法仅在将Python作为模块从包外部运行时才有效。例如:

python -m Proj

最初的折衷方法

在某些情况下,仍然会普遍使用这种方法,其中您实际上从未“安装”过软件包。例如,它在Django用户中很受欢迎。

您可以将Common/添加到sys.path(Python查找要导入的路径列表)中:

import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'Common'))
import Common

os.path.dirname(__file__) 只会返回当前 Python 文件所在的目录,然后我们进入 'Common/' 目录并导入 'Common' 模块。


5
不要手动修改Python模块路径,这样做可能只适用于快速hack。学习使用distutils、setuptools等Python包管理工具通常是必要的技能,可以解决这种问题。 - Sascha Gottfried
1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Dave
59
谢谢您回答实际问题,而不是传授正确技术的道理。有许多很好的理由来执行相对导入。 - shrewmouse
21
要再提升一个级别,每个级别需要增加一个点。例如,from ...myfile 变成 ../../myfile - WattsInABox
6
@WattsInABox,您如何进入并访问不同目录中的文件,比如说相当于../../mydir2/myfile - Aaron John Sabu
显示剩余2条评论

12

有趣的是,我遇到了同样的问题,并通过以下方式解决:

结合Linux命令ln,我们可以使事情变得更简单:

1. cd Proj/Client
2. ln -s ../Common ./

3. cd Proj/Server
4. ln -s ../Common ./

如果你想从文件中导入Proj/Common/Common.pysome_stuff到你的文件Proj/Client/Client.py,可以像这样:

# in Proj/Client/Client.py
from Common.Common import some_stuff

对于Proj/Server,同样适用,对于setup.py流程也是如此, 这里讨论了同样的问题,希望能有所帮助!


11

进行相对导入完全没问题!这是我这个小家伙的做法:

#first change the cwd to the script path
scriptPath = os.path.realpath(os.path.dirname(sys.argv[0]))
os.chdir(scriptPath)

#append the relative location you want to import from
sys.path.append("../common")

#import your module stored in '../common'
import common.py

2
但是你最好知道 sys.argv[0] 实际指向的位置 - 它(可能)不是你启动 Python 时所在的目录。 - CarlH
这是一个快速的hack,有很多陷阱。但问题本身也不怎么好。 - Sascha Gottfried
1
这篇文章写得很清楚,但是Dave的回答中的原始技巧更好,因为它使用__file__来获取当前文件的正确关系。 - John Neuhaus

11

不要使用相对导入。

根据 PEP8 的规定:

强烈不建议在包内部使用相对导入。

将所有代码放入一个超级包中(例如 "myapp"),并使用子包来处理客户端、服务器和公共代码。

更新: "Python 2.6 和 3.x 支持正确的相对导入 (...)"。有关详细信息,请参见 戴夫的回答


1
想象一下,在客户端和服务器的“if name == "main":”行之后添加一些代码。也就是说,您希望能够将它们作为独立脚本使用。如何正确地实现?我认为这是一个完全常见的用例,应该得到支持。为什么会被反对呢? - Jabba
99
“不要做这件事”竟然是一个“如何做……”问题的被接受答案让我感到惊讶(嗯,除了Ruby on Rails)。有时候确实有做这个事情的原因。我使用的解决方案与Dave建议的类似。 - Tom Wilson
1
@TomWilson:这不是纯粹的“不要这样做”的回答。下面有“用这种方式来做”的建议。 - Michał Šrajer
3
应该有人告诉Numpy的开发人员,他们使用了大量的相对导入! - Austin A
18
这个答案不适用于当前版本的Python。PEP 8中引用的部分已经不存在了。现在它的内容是:“显式相对导入是绝对导入的可接受替代方案,特别是当处理复杂的包布局时,使用绝对导入会使代码过于冗长。” - moooeeeep
显示剩余2条评论

6

默认的导入方法已经是“相对”的,来自PYTHONPATH。 PYTHONPATH默认情况下包括一些系统库以及源文件所在文件夹。如果你使用-m运行模块,则当前目录会被添加到PYTHONPATH中。因此,如果您的程序入口点位于Proj内部,则在Server.py和Client.py中使用import Common.Common应该都可以正常工作。

不要使用相对导入,它不会按照您想要的方式工作。


1
如果这是真的,为什么最佳答案没有说到这一点?这个方法是否可行? - pretzlstyle

3

Python世界新手的简单答案

创建一个简单的示例

假设我们在当前工作目录中运行ls -R,这是结果:

./second_karma:
enemy.py  import.py  __init__.py  math

./second_karma/math:
fibonacci.py  __init__.py

我们运行这个命令:$ python3 second-karma/import.py

init.py是一个空文件,但它应该存在。

现在让我们看看second-karma/import.py里面有什么:

from .math.fibonacci import Fibonacci
fib = Fibonacci()
print(fib.get_fibonacci(15))

第二个文件 second_karma/math/fibonacci.py 中包含什么内容:

from ..enemy import Enemy
class Fibonacci:
    enemy: Enemy

    def __init__(self):
        self.enemy = Enemy(150,900)
        print("Class instantiated")
    
    def get_fibonacci(self, which_index: int) -> int:
        print(self.enemy.get_hp())
        return 4

现在的最后一个文件是second_karma/enemy.py:
class Enemy:
    hp: int = 100
    attack_low: int = 180
    attack_high: int = 360

    def __init__(
            self, 
            attack_low: int,
            attack_high: int) -> None: 
        self.attack_low = attack_low
        self.attack_high = attack_high

    def getAttackPower(
            self) -> {"attack_low": int, "attack_high": int}:
        return {
            "attack_low": self.attack_low,
            "attack_high": self.attack_high
        }

    def get_hp(self) -> int:
        return self.hp

现在,简单回答为什么它没有起作用:
  • Python has a concept of packages, which is basically a folder containing one or more modules, and zero-or-more packages.
  • When we launch python, there are two ways of doing it:
    • Asking python to execute a specific module (python3 path/to/file.py).
    • Asking python to execute a package.
  • The issue is that import.py makes reference to importing .math
    • The .math in this context means "go find a module/package in the current package with the name math"
    • Trouble:
      • When I execute as $ python3 second-karma/import.py I am executing a module, not a package. thus python has no idea what . means in this context
      • Fix:
        python3 -m second_karma.import
        
      • Now import.py is of parent package second_karma, and thus your relative import will work.

重要提示:

这些 __init__.py 是必需的,如果没有它们,您必须先创建它们。

一个在Github上的示例


1
我使用的方法与上面提到的Gary Beardsley类似,只有小的变化。 文件名:Server.py
import os, sys
script_path = os.path.realpath(os.path.dirname(__name__))
os.chdir(script_path)
sys.path.append("..")
# above mentioned steps will make 1 level up module available for import
# here Client, Server and Common all 3 can be imported.

# below mentioned import will be relative to root project
from Common import Common
from Client import Client

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接