Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The way to make namespace packages in Python

From Namespace Packages in distribute, I know I can make use of namespace packages to separate a big Python package into several smaller ones. It is really awesome. The document also mentions:

Note, by the way, that your project’s source tree must include the namespace packages’ __init__.py files (and the __init__.py of any parent packages), in a normal Python package layout. These __init__.py files must contain the line:

__import__('pkg_resources').declare_namespace(__name__)

This code ensures that the namespace package machinery is operating and that the current package is registered as a namespace package.

I'm wondering are there any benefits to keep the same hierarchy of directories to the hierarchy of packages? Or, this is just the technical requirement of the namespace packages feature of distribute/setuptools?

Ex,

I would like to provide a sub-package foo.bar, such that I have to build the following hierarchy of folders and prepare a __init__.py to make setup.py work the namespace package:

~foo.bar/
~foo.bar/setup.py
~foo.bar/foo/__init__.py    <=    one-lined file dedicated to namespace packages
~foo.bar/foo/bar/__init__.py
~foo.bar/foo/bar/foobar.py

I'm not familiar with namespace packages but it looks to me that 1) foo/bar and 2) (nearly) one-lined __init__.py are routine tasks. They do provide some hints of "this is a namespace package" but I think we already have that information in setup.py?

edit:

As illustrated in the following block, can I have a namespace package without that nested directory and one-lined __init__.py in my working directory? That is, can we ask setup.py to automatically generate those by just putting one line namespace_packages = ['foo']?

~foo.bar/
~foo.bar/setup.py
~foo.bar/src/__init__.py    <=    for bar package
~foo.bar/src/foobar.py
like image 200
Drake Guan Avatar asked Dec 05 '11 02:12

Drake Guan


1 Answers

A namespace package mainly has a particular effect when it comes time to import a sub-package. Basically, here's what happens, when importing foo.bar

  • the importer scans through sys.path looking for something that looks like foo.
  • when it finds something, it will look inside of the discovered foo for bar.
  • if bar is not found:
    1. if foo is a normal package, an ImportError is raised, indicating that foo.bar doesn't exist.
    2. if foo is a namespace package, the importer goes back to looking through sys.path for the next match of foo. the ImportError is only raised if all paths have been exhausted.

So that's what it does, but doesn't explain why you might want that. Suppose you designed a big, useful library (foo) but as part of that, you also developed a small, but very useful utility (foo.bar) that others python programmers find useful, even when they don't have a use for the bigger library.

You could distribute them together as one big blob of a package (as you designed it) even though most of the people using it only ever import the sub-module. Your users would find this terribly inconvenient because they'd have to download the whole thing (all 200MB of it!) even though they are only really interested in a 10 line utility class. If you have an open license, you'll probably find that several people end up forking it and now there are a half dozen diverging versions of your utility module.

You could rewrite your whole library so that the utility lives outside the foo namespace (just bar instead of foo.bar). You'll be able to distribute the utility separately, and some of your users will be happy, but that's a lot of work, especially considering that there actually are lots of users using the whole library, and so they'll have to rewrite their programs to use the new.

So what you really want is a way to install foo.bar on its own, but happily coexist with foo when that's desired too.

A namespace package allows exactly this, two totally independent installations of a foo package can coexist. setuptools will recognize that the two packages are designed to live next to each other and politely shift the folders/files in such a way that both are on the path and appear as foo, one containing foo.bar and the other containing the rest of foo.

You'll have two different setup.py scripts, one for each. foo/__init__.py in both packages have to indicate that they are namespace packages so the importer knows to continue regardless of which package is discovered first.

like image 143
SingleNegationElimination Avatar answered Sep 22 '22 08:09

SingleNegationElimination