22

Is this a good practice in Python (from Active State Recipes -- Public Decorator)?

import sys

def public(f):
  """Use a decorator to avoid retyping function/class names.

  * Based on an idea by Duncan Booth:
  http://groups.google.com/group/comp.lang.python/msg/11cbb03e09611b8a
  * Improved via a suggestion by Dave Angel:
  http://groups.google.com/group/comp.lang.python/msg/3d400fb22d8a42e1
  """
  all = sys.modules[f.__module__].__dict__.setdefault('__all__', [])
  if f.__name__ not in all:  # Prevent duplicates if run from an IDE.
      all.append(f.__name__)
  return f

public(public)  # Emulate decorating ourself

The general idea would be to define a decorator that takes a function or class and adds its name to the __all__ of the current module.

Josh Lee
  • 171,072
  • 38
  • 269
  • 275
Ed L
  • 1,947
  • 2
  • 17
  • 30
  • 2
    Fixed the title, as some people pointed out – Ed L Jun 01 '11 at 18:57
  • While this seems like a nice idea, I find that it confuses my IDE (PyCharm 2016.1.4), which mostly defeats the purpose. Given adequate IDE support, I would use it. – Christopher Barber Jul 13 '16 at 20:22
  • Getting this decorator 100% bullet-proof appears to be harder: see the [Python bug #26632](https://bugs.python.org/issue26632) and the [`atpublic` module](https://pypi.python.org/pypi/atpublic) mentioned there. – kostix Dec 10 '16 at 09:25
  • Cross reference: I've [cited your decorator](http://stackoverflow.com/a/41895257/1468366) (with one name change) in a CW answer to the question of how to write such a decorator. – MvG Jan 27 '17 at 14:05

4 Answers4

17

The more idiomatic way to do this in Python is to mark the private functions as private by starting their name with an underscore:

def public(x):
      ...


def _private_helper(y):
    ...

More people will be familiar with this style (which is also supported by the language: _private_helper will not be exported even if you do not use __all__) than with your public decorator.

luispedro
  • 6,934
  • 4
  • 35
  • 45
  • 2
    This doesn't seem to address the problem the decorator solves: keeping the names in `__all__` up to date. – Ed L Jun 01 '11 at 20:18
  • 4
    @EdL That's because if you consistently use underscores for "private" things and no underscores for public things, you won't need `__all__` at all – Tobias Kienzler May 15 '13 at 08:02
  • 1
    @TobiasKienzler Won't any imported modules be exported? You probably still want to block those. – Neil G Oct 23 '15 at 08:08
  • 1
    @NeilG you can `import module as _module` – Tobias Kienzler Oct 23 '15 at 09:59
  • 1
    @TobiasKienzler: Good idea, but that is definitely not common practice – Neil G Oct 23 '15 at 09:59
  • 2
    @NeilG Neither is the `@public` decorator proposed, _common_ practice is using `__all__` as intended – Tobias Kienzler Oct 23 '15 at 18:49
  • @TobiasKienzler Sure, but `@public` is a world better than prepending an underscore to every usage of every module. – Neil G Oct 23 '15 at 18:52
  • 1
    @NeilG You can also clean up your imports via an explicit `del module`, but whether _that_ is truly better I dare to doubt... – Tobias Kienzler Oct 23 '15 at 18:54
  • @TobiasKienzler You can't just `del module` at the end, your exported functions that use `module` won't work! – flornquake Jun 06 '16 at 00:55
  • 3
    @TobiasKienzler @NeilG [Parts of the standard library](https://hg.python.org/cpython/file/3.5/Lib/collections/__init__.py) use the pattern `import module as _module`, so I'd call it a fairly established practice. – flornquake Jun 06 '16 at 00:59
  • @flornquake Good point about `del module`. And about `import as _`. Good to know :) – Tobias Kienzler Jun 07 '16 at 05:42
8

Yes, it's a good practice. This decorator allows you to state your intentions right at function or class definition, rather than directly afterwards. That makes your code more readable.

@public 
def foo():
    pass 

@public 
class bar():
    pass

class helper(): # not part of the modules public interface! 
    pass

Note: helper is still accessible to a user of the module by modulename.helper. It's just not imported with from modulename import *.

Steven Rumbalski
  • 44,786
  • 9
  • 89
  • 119
  • 8
    I wouldn't necessarily say it's a good practice outright. The advantage of having an `__all__` explicitly defined at the beginning of a module is that anyone (possibly you) looking at it can very easily determine what is exported via `import *` and what is part of the public API of the module. Using a public decorator makes this much more difficult. However, if you use a decorator, you can tell while looking at a member of the module whether it is in `__all__` or not. So both have their respective pros and cons. – darkfeline Mar 19 '13 at 23:58
  • Everything darkfeline said plus - it breaks intellisense/code-completion on IDEs with naive code tokenizers. – synthesizerpatel Nov 30 '13 at 05:06
1

This doesn't automatically add names to __all__, it simply allows you to add a function to all by decorating it with @public. Seems like a nice idea to me.

LaC
  • 12,624
  • 5
  • 39
  • 38
1

I think the question is a bit subjective, but I like the idea. I usually use __all__ in my modules but I sometimes forget to add a new function that I intended to be part of the public interface of the module. Since I usually import modules by name and not by wildcards, I don't notice the error until someone else in my team (who uses the wildcard syntax to import the entire public interface of a module) starts to complain.

Note: the title of the question is misleading as others have already noticed among the answers.

Tamás
  • 47,239
  • 12
  • 105
  • 124