Hiding implementation files in a package

人盡茶涼 提交于 2019-12-10 21:27:27

问题


I have a module called spellnum. It can be used as a command-line utility (it has the if __name__ == '__main__': block) or it can be imported like a standard Python module.

The module defines a class named Speller which looks like this:

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

As you can see, the class constructor loads other modules at runtime. Those modules (spelling_en.py, spelling_es.py, etc.) are located in the same directory as the spellnum.py itself.

Besides spellnum.py, there are other files with utility functions and classes. I'd like to hide those files since I don't want to expose them to the user and since it's a bad idea to pollute the Python's lib directory with random files. The only way to achieve this that I know of is to create a package.


I've come up with this layout for the project (inspired by this great tutorial):

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

The file __init__.py contains a single line:

from spellnum import Speller

Given this new layout, the code for dynamic module loading had to be changed:

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

So, with this project layout a can do the following:

  1. Successfully import spellnum inside example.py and use it like a simple module:

    # an excerpt from the example.py file
    import spellnum
    
    speller = spellnum.Speller(es)
    # ...
    
  2. import spellnum in the tests and run those tests from the project root like this:

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

The problem

I cannot execute spellnum.py directly with the new layout. When I try to, it shows the following error:

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'

The question

What's the best way to organize all of the files required by my module to work so that users are able to use the module both from command line and from their Python code?

Thanks!


回答1:


How about keeping spellnum.py?

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



回答2:


Your problem is, that the package is called the same as the python-file you want to execute, thus importing

from spellnum import spellnum_en

will try to import from the file instead of the package. You could fiddle around with relative imports, but I don't know how to make them work with __import__, so I'd suggest the following:

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


来源:https://stackoverflow.com/questions/8769991/hiding-implementation-files-in-a-package

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!