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

24 投票
4 回答
86333 浏览
提问于 2025-04-16 12:37

这是我正在使用的结构:

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

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

from subdir.myclass01 import *

对于在 myclass01.py 中定义的类,这样是可以正常工作的。但是如果有很多类分散在不同的文件中,比如在 subdir 文件夹里,而我想一次性导入所有这些类,那我就得为每个文件都写一行代码。这肯定有更简单的方法。我试过:

from subdir.* import *

但这样并没有成功。

编辑:以下是文件的内容:

这是 __init__.py(使用 __all__,正如 Apalala 建议的那样):

__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'

4 个回答

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 文件中导入内容,然后只把公共的部分放到全局命名空间里。此外,它还允许你设置过滤条件,如果你只想要某些特定的公共属性。

6

你最好的选择,虽然可能不是最好的写法,就是把所有东西都导入到这个包的命名空间里:

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

然后,在其他模块中,你可以直接从这个包里导入你想要的东西:

from subdir import Class1
12

虽然这里用的名字和你问题中的目录结构不太一样,但你可以参考我在命名空间和类这个问题中的回答。那里的__init__.py文件也可以让usepackage.py脚本这样写(package对应你问题中的subdirClass1对应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自己。

撰写回答