开发者

How to keep submodule names out of the name space of a Python package?

开发者 https://www.devze.com 2023-02-16 15:29 出处:网络
I want the interface of some module to contain a certain number of functions and classes (and nothing else).I could implement all of 开发者_开发问答those in a single file, and would easily get the int

I want the interface of some module to contain a certain number of functions and classes (and nothing else). I could implement all of 开发者_开发问答those in a single file, and would easily get the interface I want. But since there is a lot of code, I'd prefer to split the whole thing up into several files, say

mypackage/
    __init__.py
    a.py
    b.py
    c.py
    d.py

To get the desired interface anyway, I define an __init__.py file for the package that imports all public symbols from a, b, c and d:

from a import func_a1, func_a2, ClassA1, ClassA2
from b import func_b1, func_b2, ClassB1, ClassB2
from c import func_c1, func_c2, ClassC1, ClassC2
from d import func_d1, func_d2, ClassD1, ClassD2

If I import the package using

import mypackage

the package namespace also contains the symbols a, b, c and d. These names are implementation details and not part of my interface. I don't want them to appear as "public" symbols. What is the best way of getting rid of them?

The options I considered are

  1. Use a single module instead of a package. The interface will look fine, but the implementation will get less clear than it is now.

  2. Add the line

    del a, b, c, d
    

    to the end of __init__.py. Works ok, but seems like a hack. (For example, you can't import __init__ any more, which works without this line.)

  3. Rename a, b, c and d to _a, _b, _c and _d. Now they are included in mypackage's namespace as "private" symbols, which I'm fine with, but it feels a bit strange that all my filenames start with an underscore (in reality, there are of course more than four submodules).

Any better suggestions? Or thoughts on which option to prefer?

Or am I just to anal and shouldn't care about the whole thing?


If some of the files in a package are indeed implementation details, go ahead and stick an underscore in front of them -- that's what we use them for.

For example, if you look in ctypes you'll see

__init__.py
==================================================
"""create and manipulate C data types in Python"""

import os as _os, sys as _sys

__version__ = "1.1.0"

from _ctypes import Union, Structure, Array
from _ctypes import _Pointer
from _ctypes import CFuncPtr as _CFuncPtr
...

As you can see, even os and sys became implementation details in that file.


If you really want to remove the names from the namespace then you can just use the del statement on them and they'll disappear like the wind.


Here's a solution inspired by Javascript single-function modules:

def __init__module():
    from os import path

    def _module_export_1():
        return path.abspath('../foo')

    def _module_export_2():
        return path.relpath('foo/bar', 'foo')

    g = globals()
    g['module_export_1'] = _module_export_1
    g['module_export_2'] = _module_export_2

__init__module()

Although the module needs to import 'path' from os, 'path' doesn't pollute the module namespace. The only cruft in the module namespace is __init_module(), which is clearly marked private by the double-underscore prefix.

Another option would be to import needed modules at the top of each function, rather than the top of your module. After the first time a module is imported, subsequent imports are just a lookup in the sys.modules dictionary.

But I agree with the other commenters here -- the Python convention is not to worry about module namespace pollution, and just make it obvious to your module's users which parts of the namespace are your public API and which are internals.


From http://docs.python.org/tutorial/modules.html:

The import statement uses the following convention: if a package’s __init__.py code defines a list named __all__, it is taken to be the list of module names that should be imported when from package import * is encountered.

In your mypackage/__init__.py, try adding this:

# add this line, replace "..." with the rest of the definitions
# you want made public
__all__ = ['func_a1', 'func_a2', 'ClassA1', 'ClassA2', ...]

from a import func_a1, func_a2, ClassA1, ClassA2
from b import func_b1, func_b2, ClassB1, ClassB2
from c import func_c1, func_c2, ClassC1, ClassC2
from d import func_d1, func_d2, ClassD1, ClassD2
0

精彩评论

暂无评论...
验证码 换一张
取 消