在包中隐藏实现文件

3 投票
2 回答
1686 浏览
提问于 2025-04-17 09:44

我有一个叫做 spellnum 的模块。它可以作为命令行工具使用(因为里面有 if __name__ == '__main__': 这一部分),也可以像普通的 Python 模块一样被导入。

这个模块定义了一个叫 Speller 的类,长得像这样:

class Speller(object):
    def __init__(self, lang="en"):
        module = __import__("spelling_" + lang)
        # use module's contents...

你可以看到,这个类的构造函数在运行时会加载其他模块。这些模块(比如 spelling_en.pyspelling_es.py 等)和 spellnum.py 在同一个文件夹里。

除了 spellnum.py,还有其他一些文件里有一些工具函数和类。我想把这些文件隐藏起来,因为我不想让用户看到它们,而且把这些随机文件放到 Python 的库目录里也不是个好主意。我知道的唯一方法就是创建一个包。


我为这个项目想出了这样的布局(灵感来自这个很棒的 教程):

spellnum/                # project root
    spellnum/            # package root
        __init__.py
        spellnum.py
        spelling_en.py
        spelling_es.py
        squash.py
        # ... some other private files
    test/
        test_spellnum.py
    example.py

文件 __init__.py 里只有一行:

from spellnum import Speller

有了这个新的布局,动态加载模块的代码也需要改一下:

class Speller(object):
    def __init__(self, lang="en"):
        spelling_mod = "spelling_" + lang
        package = __import__("spellnum", fromlist=[spelling_mod])
        module = getattr(package, spelling_mod)
        # use module as usual

所以,按照这个项目布局,我可以做到以下几点:

  1. example.py 中成功 import spellnum,并像简单模块一样使用它:

    # an excerpt from the example.py file
    import spellnum
    
    speller = spellnum.Speller(es)
    # ...
    
  2. 在测试中 import spellnum,并从项目根目录运行这些测试,像这样:

    $ PYTHONPATH="`pwd`:$PYTHONPATH" python test/test_spellnum.py
    

问题

我无法直接用新的布局执行 spellnum.py。当我尝试时,出现了以下错误:

Traceback (most recent call last):
  ...
  File "spellnum/spellnum.py", line 23, in __init__
    module = getattr(package, spelling_mod)
AttributeError: 'module' object has no attribute 'spelling_en'

问题

有什么好的方法来组织我模块所需的所有文件,以便用户可以从命令行和他们的 Python 代码中使用这个模块呢?

谢谢!

2 个回答

1

你的问题是,包的名字和你想要执行的Python文件名字是一样的,这样在导入的时候

from spellnum import spellnum_en

就会尝试从这个文件中导入,而不是从包中导入。你可以尝试使用相对导入的方法,但我不知道怎么和__import__一起使用,所以我建议你试试下面的方法:

def __init__(self, lang="en"):
    mod = "spellnum_" + lang
    module = None
    if __name__ == '__main__':
        module = __import__(mod)
    else:
        package = getattr(__import__("spellnum", fromlist=[mod]), mod)
2

那我们要不要保留一下 spellnum.py 这个文件呢?

spellnum.py
spelling/
  __init__.py
  en.py
  es.py

撰写回答