__init__.py 导入默认类。

5

假设我有ClassA、ClassB和ClassC,它们分别在它们自己的.py文件中,称为ClassA.pyClassB.pyClassC.py

# Inside ClassA.py
class ClassA:
    pass

在我的__init__.py中,最终我得到的是:
from .ClassA import ClassA
from .ClassB import ClassB
from .ClassC import ClassC

每次我添加一个新类(遵循相同的结构),我都需要更改 __init__.py 文件。有没有推荐的方法来自动化这个过程,考虑到该包中所有文件都遵循相同的(不太符合Python惯例但没关系)结构?
每次添加新类时(使用相同的结构),都需要更改 __init__.py 文件。是否有推荐的自动化方式,假设该包中所有文件都遵循相同的(不太符合Python规范但也不要紧)结构?

在Python中,将每个类放在一个ClassName.py文件中是一个不好的想法。如果你决定采用每个文件只有一个类的结构,请至少将其命名为classname.py而不是ClassName.py。将类和模块命名为相同的名称会导致奇怪的依赖于导入顺序的问题,有时你会得到类,有时你会得到模块。 - user2357112
嗯...我不确定如何理解这个评论。我一直使用这种结构,从来没有遇到过任何问题。我在很多地方都有from com.mycompany.Foo import Foo的调用。也许是类名的完全限定名,包括它的包名,让我免于麻烦。我确实认为我在其他包中经常遇到这种模式,但是再次强调,也许没有以这种方式导入。所以我并不是说你错了。我只是在多年的Python编程中没有遇到过这个问题。 - CryptoFool
我也看不出来这会有什么问题。在 from X import Y 中,X 不是总是一个包名而 Y 则总是一个符号名吗?我想不出这两者会有任何歧义的情况。 - CryptoFool
@Steve:from com.mycompany.Foo import Foo 是足够安全的,但是看一下问题中的代码如何在 __init__.py 中执行 from .ClassA import ClassA。这样做的整个目的是使 from package import ClassA 导入类,但是根据导入顺序,它可能会导入类或模块。 - user2357112
这种用 Python 写 Java 的风格会引起许多其他问题。例如,它会增加循环导入的问题。如果同一文件中的两个类相互依赖,执行顺序是直接的。但如果你把每个类都放在自己的文件中,你最终需要进行循环导入,并伴随着所有相关初始化顺序的问题。 - user2357112
啊!好的例子。我希望你能这样做,因为我认为你知道自己在说什么。顺便说一下,当我这样做时(我不得不回头看),它是为了完全动态的需求...像Chef系统一样,用户可以放入新的.py文件来描述新的包。我们使用小写的包/文件名,并且内部的类始终是一个Package对象,所以我们的情况与此完全不同。很高兴你能来阐明这个特定的用例。 - CryptoFool
2个回答

3

可以,但不建议这么做。

考虑以下目录结构:

├── test_module
│   ├── ClassA.py
│   ├── ClassB.py
│   └── __init__.py

我们希望以编程方式将ClassA.ClassAClassB.ClassB导入到__init__.py中。
假设这是ClassA.py的内容:
class ClassA:
    pass

ClassB.py 与之前的 class 名称不同,其他完全相同。

现在,假设我们想要在 __init__.py 中以非递归方式遍历根目录(尽管没有目录,但并不影响),并从所有模块中导入所有 classes

__init__.py

最初的回答:

import os
from importlib import import_module

my_location = os.path.dirname(__file__)
module_list = [file
               for file in os.listdir(my_location)
               if os.path.splitext(file)[1] == '.py'
               and file != '__init__.py']
modules = [import_module(f'.{os.path.splitext(module)[0]}', __name__)
           for module in module_list]

运行import test_module后,ClassA.pyClassB.py中的classes将作为test_module.ClassA.ClassAtest_module.ClassB.ClassB导入到工作空间中。
举个例子:
>>> import test_module
>>> test_module.ClassA.ClassA()
<test_module.ClassA.ClassA object at 0x7f1e66181fd0>

为了完整性,如果您希望此脚本模仿 from X import Y 的行为:
globals().update({name: getattr(module, name)
                  for module in modules
                  for name in module.__dict__
                  if not name.startswith('_')})

导入:

>>> import test_module
>>> test_module.ClassA()
<test_module.ClassA.ClassA object at 0x7fb8edb9dfd0>

这将使得这些名称可以作为test_module.ClassA等访问(因为您是从test_module导入的,我们添加了一个额外的间接层)。在test_module的范围内,它们可以直接作为未限定名称访问。
我们可以添加其他功能,比如检查每个模块的__all__属性并执行子目录的递归遍历等,但这已经超出了这个问题的范围。我必须强调的是,在我看来,最好的做法是重构您的代码,以便首先不需要这样做,而不是添加与Python内部交互的功能,这似乎并不是必要的。

1

是的,你可以。这就是 importlib 包的作用。它包含了实际执行导入操作的低级调用。它允许您通过调用提供的方法来进行导入。

因此,您可以使用 Python os.path.listdir 或其他方法来了解您的子包,并调用 importlib 来导入每个子包。

我自己并没有做过太多这方面的工作。只是知道这个存在。也许其他人可以给您更多细节。此外,这个链接可能对您有所帮助:

https://dev.to/0xcrypto/dynamic-importing-stuff-in-python--1805


根据评论和其他答案,我只想提醒读者我并没有说“应该”...只是“可以”。我的用例...我知道importlib的原因...非常不同。那里没有可能出现名称冲突,而且它是完全不同的用例。这就引出了一个问题...在这种特定的用例中,什么才是正确的方法呢?似乎想要做到这一点是合理的。 - CryptoFool
我认为正确的做法是重构你的代码,这样你就不需要一开始就这么做了。说实话,这似乎更像Java而不是Python,正如另一个用户所说。(因为个人而言,我想不出一个必须要这样做的情况) - gmds
如果我们谈论的是同一个程序员或团队负责外部模块和内部类,我完全同意。如果只是为了节省每个可能出现的5-10个类的_init_.py中的一行代码,那么我认为这不值得。如果这更像是一个可扩展的平台,其他程序员可能只想要添加“扩展功能”,那么我可以考虑使用类似的东西。但在这种情况下,我认为你会希望这些扩展功能存在于主模块之外。因此,我也想不到添加这种复杂性的明确原因。 - CryptoFool
抱歉,我不得不关闭我的dev.to账户和博客。因此,答案中的链接将显示404错误。我已经在我的新博客https://hackberry.xyz/dynamic-importing-stuff-in-python上重新发布了文章。 - 0xcrypto

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