从子目录中的不同文件导入类

24

这是我正在使用的结构:

directory/
          script.py
          subdir/
                 __init__.py
                 myclass01.py
                 myclass02.py

我想要做的是在script.py中导入myclass01.py和myclass02.py中定义的类。如果我这样做:

from subdir.myclass01 import *

对于在myclass01.py中定义的类,这个解决方案可以正常工作。但是,如果在subdir中有多个不同文件中定义的类需要导入,我就必须为每个文件都输入一行代码。一定有一个快捷方式。我尝试过:

from subdir.* import *

但是它没有奏效。

编辑:这里是文件的内容:

这是__init__.py文件(使用Apalala建议的__all__):

__all__ = ['MyClass01','MyClass02']

这是 myclass01.py 文件:

class MyClass01:
    def printsomething():
        print 'hey'

这是myclass02.py

class MyClass02:
    def printsomething():
        print 'sup'

这是script.py文件:

from subdir import *
MyClass01().printsomething()
MyClass02().printsomething()

当我尝试运行 script.py 时,我得到的是以下回溯信息:

File "script.py", line 1, in <module>
    from subdir import *
AttributeError: 'module' object has no attribute 'MyClass01'

1
我不知道这是如何实现的,但是不要这样做。明确导入每个模块。如果你这样做,以后会遇到各种问题。在我看来,这是不值得的。 - user225312
这可能会带来哪些问题? - liewl
我不知道@user225312警告你的是什么问题,但我在许多场合使用了类似于我回答中的代码而没有遇到任何问题。事实上,我认为这是一个相当好的方法,可以避免将东西硬编码到脚本中,同时也是实现插件软件架构的一种不错的方式。 - martineau
1
我想这个链接做得最好。https://dev59.com/KG855IYBdhLWcg3wy3ob - Soham Bhattacharya
5个回答

12

虽然那里使用的名称与您问题所示的目录结构不同,但您可以使用我在题为Namespacing and classes的答案。那里显示的__init__.py也将允许以这种方式编写usepackage.py脚本(package对应于您问题中的subdir,而Class1对应于myclass01,以此类推):

from package import *

print Class1
print Class2
print Class3

修订(更新):

哎呀,抱歉,我之前的答案中的代码并不完全符合你的要求——它只会自动导入任何包子模块的名称。要使其还能导入每个子模块的指定属性需要添加几行代码。这是包的__init__.py文件的修改版本(也适用于Python 3.4.1):

def _import_package_files():
    """ Dynamically import all the public attributes of the python modules in this
        file's directory (the package directory) and return a list of their names.
    """
    import os
    exports = []
    globals_, locals_ = globals(), locals()
    package_path = os.path.dirname(__file__)
    package_name = os.path.basename(package_path)

    for filename in os.listdir(package_path):
        modulename, ext = os.path.splitext(filename)
        if modulename[0] != '_' and ext in ('.py', '.pyw'):
            subpackage = '{}.{}'.format(package_name, modulename) # pkg relative
            module = __import__(subpackage, globals_, locals_, [modulename])
            modict = module.__dict__
            names = (modict['__all__'] if '__all__' in modict else
                     [name for name in modict if name[0] != '_'])  # all public
            exports.extend(names)
            globals_.update((name, modict[name]) for name in names)

    return exports

if __name__ != '__main__':
    __all__ = ['__all__'] + _import_package_files()  # '__all__' in __all__

或者你可以将上述内容放入包目录中一个独立的 .py 模块文件中,例如_import_package_files.py,并从包的 __init__.py 中使用它,像这样:

if __name__ != '__main__':
    from ._import_package_files import *  # defines __all__
    __all__.remove('__all__')  # prevent export (optional)

无论你给文件命名为什么,它都应该以一个_下划线字符开头,这样它就不会递归地尝试import自己。


7
哇,这个问题的解决方案相当复杂。 - liewl
当我在script.py中尝试调用MyClass01().printsomething()方法时,它会显示名称MyClass01未定义。我猜导入过程出了问题。 - liewl
它起作用了。有没有不必在前面使用 myclass01. 的方法? - liewl
@David McDavidson:是的,请查看我的修订答案,它还将在命名空间中导入myclass01.MyClass01(而不仅仅是myclass0)。 - martineau
@David McDavidson:很高兴听到这个消息。请注意,当前的写法忽略了模块和包的任何__all__属性,该属性通常确定在使用*时导入哪些名称是公共的。虽然可以做到这一点,但这样做可能会进一步复杂化代码。 - martineau
显示剩余10条评论

6

你最好的选择,虽然可能不是最好的风格,是将所有内容导入到包的命名空间中:

# this is subdir/__init__.py
from myclass01 import *
from myclass02 import *
from myclass03 import *

然后,在其他模块中,您可以直接从包中导入所需内容:

from subdir import Class1

1
最初我把所有的类都放在一个文件里。由于这些类变得越来越大,我将它们分别放在不同的文件中,并将它们放在一个目录中。那么,有什么符合Python风格的方法可以做到这一点呢? - liewl
好的,使用您在答案中提供的解决方案是有效的。然后我注释掉了from ... import *语句,并添加了__all__ = ['myclass01','myclass02']。在script.py中调用from subdir import *是应该可以工作的,对吧?但它并没有起作用。 - liewl
@David McDavidson 请提供更具体的示例。您最初的问题没有提及类名。无论如何,__all__ 应该包含类名,而不是私有模块名。 - Apalala
我已经更新了问题,包括类定义和结果的回溯。 - liewl
从错误信息来看,似乎您忘记在 subdir/__init__.py 中添加 from myclass01 import * - Apalala
显示剩余3条评论

6

我知道这个问题已经有几个月没有被回答了,但是我正在寻找同样的内容并发现了这个页面。我对所选择的答案不太满意,于是我写了自己的解决方案并想分享一下。以下是我的解决方案:

# NOTE: The function name starts with an underscore so it doesn't get deleted by iself
def _load_modules(attr_filter=None):
    import os

    curdir = os.path.dirname(__file__)
    imports = [os.path.splitext(fname)[0] for fname in os.listdir(curdir) if fname.endswith(".py")]

    pubattrs = {}
    for mod_name in imports:
        mod = __import__(mod_name, globals(), locals(), ['*'], -1)

        for attr in mod.__dict__:
            if not attr.startswith('_') and (not attr_filter or attr_filter(mod_name, attr)):
                pubattrs[attr] = getattr(mod, attr)

    # Restore the global namespace to it's initial state
    for var in globals().copy():
        if not var.startswith('_'):
            del globals()[var]

    # Update the global namespace with the specific items we want
    globals().update(pubattrs)

# EXAMPLE: Only load classes that end with "Resource"
_load_modules(attr_filter=lambda mod, attr: True if attr.endswith("Resource") else False)
del _load_modules # Keep the namespace clean

这只是从包目录中的所有.py文件中导入*,然后仅将公共文件拉入全局命名空间。此外,如果只需要某些公共属性,则允许使用过滤器。


0
我使用这种简单的方法:
  1. 将目录添加到系统路径中,然后
  2. 在该目录中使用import modulefrom module import function1, class1
请注意,module只是您的*.py文件的名称,不包括扩展名部分。
以下是一个通用示例:
import sys
sys.path.append("/path/to/folder/")
import module # in that folder

在你的情况下,可能是这样的:
import sys
sys.path.append("subdir/")
import myclass01
# or
from myclass01 import func1, class1, class2 # .. etc

-2
from subdir.* import *

在“from”语句之后,你不能直接使用“*”。你需要进行显式导入。请查看 Python 文档中关于导入和包的说明。


1
这不是一个答案。 - JohnAllen

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