Another @public implementation
Project description
This is a very simple decorator and function which populates a module’s __all__ and optionally the module globals. It is a standalone version of the implementation being proposed for Python 3.6 in issue 26632 on the Python bug tracker.
This provides both a pure-Python implementation and a C implementation. It is proposed that the C implementation be added to builtins for Python 3.6.
Background
__all__ is great. It has both a functional and a documentation purpose.
The functional purpose is that it directly controls which module names are imported by the from <module> import * statement. In the absence of an __all__, when this statement is executed, every name in <module> that does not start with an underscore will be imported. This often leads to importing too many names into the module.
In the presence of an __all__, only the names specified in this list are imported by the from <module> import * statement. This in essence gives the <module> author a way to explicitly state which names are for public consumption.
And that’s the second purpose of __all__; it serves as module documentation, explicitly naming the public objects it wants to export. You can print a module’s __all__ and get an explicit declaration of its public API.
The problem
However, __all__ has two problems.
First, its separates the declaration of a name’s public export semantics from the implementation of that name. Usually the __all__ is put at the top of the module, although this isn’t required, and in some cases it’s actively prohibited. So when you’re looking at the definition of a function or class in a module, you have to search for the __all__ definition to know whether the function or class is intended for public consumption.
This leads to the second problem, which is that it’s all too easy for the __all__ to get out of sync with the module’s contents. Often a function or class is renamed, removed, or added without the __all__ being updated. Then it’s difficult to know what the module author’s intent was, and it can lead to an exception when a string appearing in __all__ doesn’t match an existing name in the module.
The solution
The solution is to provide a way to declare a name’s public-ness right at the point of its declaration, and to infer the name to export from that definition. In this way, a module’s author never explicitly sets the __all__ so there’s no way for it to get out of sync.
This package, and issue 26632, propose just such a solution, in the form of a public builtin that can be used as either a decorator, or a callable.
You’ll usually use this as a decorator, for example:
@public def foo(): pass
or:
@public class Bar: pass
If you were to print the __all__ after both of those code snippets, you’d see:
>>> print(__all__) ['foo', 'Bar']
Note that you do not need to initialize __all__ in the module, since public will do it for you. Of course, if your module already has an __all__, it will just append new names to the existing list.
The requirements to use the @public decorator are simple: the decorated thing must have a __name__ attribute. Since you’ll overwhelmingly use it to decorate functions and classes, this will always be the case.
There’s one other common use case that isn’t covered by the @public decorator. Sometimes you want to declare simple constants or instances as publicly available. You can’t use the @public decorator for two reasons: constants don’t have a __name__ and Python’s syntax doesn’t allow you to decorate such constructs.
To solve this use case, public is also a callable function accepting keyword arguments. An example makes this obvious:
public(SEVEN=7) public(a_bar=Bar())
Now if you print the module’s __all__ you’ll see:
>>> print(__all__) ['foo', 'Bar', 'SEVEN', 'a_bar']
and as should be obvious, the module contains name bindings for these constants:
>>> print(SEVEN) 7 >>> print(a_bar) <__main__.Bar object at ...>
Note: While you can use public() with multiple keyword arguments in a single call, the order of the resulting __all__ entries is undefined, due to indeterminate dictionary sort order. If order matters to you, call public() multiple times each with a single keyword argument.
Usage
To use this, just import it:
>>> from public import public
This package actually provides both a pure Python implementation and a C implementation. It is expected/hoped that the C implementation might appear in Python 3.6. By default, the import above provides you with the more efficient C implementation. If for some reason you want the pure-Python implementation just do:
>>> from public import py_public as public
Having to do this import in every module you want to use it can get pretty tedious, so what if you could put public into Python’s builtins? Then it would be available in all your code for free:
>>> from public import install >>> install()
and now you can just use @public without having to import anything in your other modules.
By default, this installs the C implementation but if you wanted to install the pure-Python version, just do:
>>> from public import py_install >>> py_install
Alternatives
This isn’t a unique approach to @public. Other implementations do exist. There are some subtle differences between this package and those others. This package:
uses keyword arguments to map names which don’t have an __name__ attribute;
can be used to bind names and values into a module’s globals;
provides both C and Python implementations;
can optionally put public in builtins.
Project details
Project home: https://gitlab.com/warsaw/public
Report bugs at: https://gitlab.com/warsaw/public/issues
Fork the code: https://gitlab.com/warsaw/public.git
Documentation: http://public.readthedocs.io/en/latest/
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.