Skip to content

Commit 28f3ef6

Browse files
committed
Edits based on PR feedback
1 parent 6948ede commit 28f3ef6

1 file changed

Lines changed: 47 additions & 29 deletions

File tree

peps/pep-0844.rst

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ Abstract
1414

1515
This PEP proposes adding two new builtin functions, ``public()`` and ``private()``, which document
1616
the public interface of a module by keeping its ``__all__`` synchronized with the names actually
17-
defined in that module. Both are used as decorators (``@public`` and ``@private``) on class and
18-
function definitions, so that a name's visibility is declared exactly once, at the point where the
19-
name is defined. ``public()`` additionally has a function call form for names that cannot be
20-
decorated, such as constants.
17+
defined to be public in that module. Both are used as decorators (``@public`` and ``@private``) on
18+
class and function definitions, so that a name's visibility is declared exactly once, at the point
19+
where the name is defined. ``public()`` additionally has a function call form for names that cannot
20+
be decorated, such as constants.
2121

2222
For example:
2323

@@ -49,11 +49,11 @@ This PEP is an adjunct to :pep:`842` and PEP 843; see `Relationship to PEP 842 a
4949
Motivation
5050
==========
5151

52-
The module global variable :attr:`~module.__all__` is the mechanism Python currently
53-
defines for declaring a module's public names. However, ``__all__`` suffers from a well known
54-
problem: it is typically defined as a separate list often far from the objects whose names are
55-
contained in it. An object defined at one point in the file is repeated as a string literal in an
56-
``__all__`` list somewhere else, usually at the top of the file.
52+
The module global variable :attr:`~module.__all__` is the mechanism Python currently defines for
53+
declaring a module's public names. However, ``__all__`` suffers from a well-known problem: it is
54+
typically defined as a separate list often far from the objects whose names are contained in it. An
55+
object defined at one point in the file is repeated as a string literal in an ``__all__`` list
56+
somewhere else, usually at the top of the file.
5757

5858
Nothing keeps the two in sync, leading to these problems:
5959

@@ -83,9 +83,9 @@ enough, and useful enough, to be spelled without a third-party dependency. Thus
8383
``__all__`` already defines the public API
8484
------------------------------------------
8585

86-
It is sometimes said that Python has no way to express which names in a module are public and which
87-
are private, and that ``__all__`` is merely a convention governing ``from spam import *``. However,
88-
the :ref:`language reference <import>` explicitly says:
86+
It is sometimes said that Python has no way to express which names in a module are public and
87+
which are private, and that ``__all__`` is merely a convention governing ``from spam import *``.
88+
However, the :ref:`language reference <import>` explicitly says:
8989

9090
The *public names* defined by a module are determined by checking
9191
the module's namespace for a variable named ``__all__``; if defined,
@@ -133,6 +133,14 @@ Specification
133133

134134
Two new builtins are added: ``public()`` and ``private()``.
135135

136+
This PEP concerns module-level visibility only. ``public()`` and ``private()`` declare which of a
137+
module's global names make up its public interface, and they do so by maintaining ``__all__``, which
138+
is defined for modules and nothing else. Visibility in any other scope is explicitly excluded:
139+
class attributes and methods, names local to a function, and names bound in nested scopes are all
140+
untouched by this proposal. Python has no ``__all__`` equivalent for those scopes, and this PEP
141+
does not propose one. Whether a method is part of a class's public interface remains, as today, a
142+
matter of naming convention and documentation.
143+
136144
``public()``
137145
------------
138146

@@ -217,17 +225,23 @@ author's intent; ``@public`` is what makes that intent observable.
217225
.. note::
218226

219227
``private()`` does *not* support a function call form, as no valid use case for it has been
220-
identified or requested by users of the ``atpublic`` module. See `Open Issues`_ for
221-
further discussion.
228+
identified or requested by users of the ``atpublic`` package. See `Open Issues`_ for further
229+
discussion.
222230

223231

224232
Restrictions
225233
------------
226234

227-
Note that because ``@public`` is primarily used to keep the ``__all__`` module global in sync, only
228-
module-level objects may be decorated. Decorating a method inside a class body is not supported,
235+
Because ``@public`` and ``@private`` exist to keep the ``__all__`` module global in sync, only
236+
module-level objects may be declared. Decorating a method inside a class body is not supported,
229237
since ``__all__`` documents module contents, not class contents.
230238

239+
Neither function inspects the scope it is called from, so this misuse is not currently diagnosed.
240+
A decorator applied to a method appends the method's name to the enclosing *module's* ``__all__``,
241+
and a function call form used in a class body binds its keywords in the module globals rather than
242+
in the class body. Neither outcome is likely to be what the author intended. Whether these cases
243+
should raise an exception instead is an `Open Issues`_ question.
244+
231245
Because ``__all__`` must be mutable for these functions to append to it, a module that assigns
232246
``__all__`` itself must assign a list. A module that wants an immutable ``__all__`` can freeze it
233247
after the last declaration with ``__all__ = tuple(__all__)``.
@@ -288,9 +302,9 @@ explicitly. Given:
288302
``SEVEN`` is bound in the module's globals by a function that reaches into its caller's frame.
289303
Nothing about that binding is visible in the syntax tree. A type checker, linter, or language
290304
server reading the source sees a bare function call and no assignment, and will therefore report
291-
``SEVEN`` as undefined at every use site. A soft keyword like ``export SEVEN = 7`` (such as
292-
proposed by :pep:`842`) has no such problem, because syntax is by construction visible to anything
293-
that parses the file. This, and not the DRY objection raised in :pep:`842`, is the real cost of
305+
``SEVEN`` as undefined at every use site. A soft keyword like ``export SEVEN = 7``, as proposed by
306+
:pep:`842`, has no such problem, because syntax is by construction visible to anything that parses
307+
the file. This, and not the DRY objection raised in :pep:`842`, is the real cost of
294308
choosing a builtin over a keyword.
295309

296310
This could easily be alleviated by future modifications to linting tools, so that they explicitly
@@ -322,8 +336,8 @@ Here the binding is a plain assignment, visible to every tool that parses Python
322336
transition aid rather than the recommended spelling, and it should not be needed for long.
323337

324338
The conclusion is that the data and type alias use cases, which are the places a decorator
325-
genuinely cannot be utilized, do not require new syntax at all. They require a function call that
326-
tools can learn to read, alleviating the need for a dedicated, new ``export`` keyword.
339+
genuinely cannot be utilized, do not require new syntax at all. A function call that tools can
340+
recognize serves just as well, without the need for a new, dedicated ``export`` keyword.
327341

328342

329343
.. _pep-844-urgency:
@@ -369,7 +383,7 @@ pressing, but because the cheaper alternative stops being available once the exp
369383

370384
.. _pep-844-performance:
371385

372-
Import time performance
386+
Import-time performance
373387
-----------------------
374388

375389
When this idea was informally floated with core developers some years ago, before either :pep:`842`
@@ -522,7 +536,7 @@ module" that pulls names out of private submodules must currently write each nam
522536
523537
``Widget`` is named once to import it, and twice more to export it. That's a big violation of DRY!
524538
Hand-maintaining ``__all__`` would name it only twice, so for re-exports specifically, ``public()``
525-
is not merely unhelpful, it is a step backwards.
539+
is not merely unhelpful, but a step backwards.
526540

527541
The decorator form of ``@public`` is unavailable here because there is nothing to decorate, and the
528542
function call form of ``public()`` requires naming the binding explicitly. This is exactly the gap
@@ -663,18 +677,18 @@ Leaving ``atpublic`` on PyPI is the status quo option. Users who want to opt in
663677
functionality can simply add that library as a dependency and import the functions (or use the
664678
``pip install atpublic[install]`` extra to populate builtins).
665679

666-
However, if this *is* a problem worth solving now, then leaving this in a third-party module on PyPI
667-
doesn't serve our users adequately. The need to include a dependency and an explicit import may
668-
be just enough of a hurdle (albeit small) to stop widespread use of it. Adding it to builtins gives
669-
this pattern a promotional endorsement that will gain in popularity.
680+
However, if this *is* a problem worth solving now, then leaving this in a third-party package on
681+
PyPI doesn't serve our users adequately. The need to include a dependency and an explicit import
682+
may be just enough of a hurdle (albeit small) to stop widespread use of it. Adding it to builtins
683+
endorses the pattern in a way that should broaden its adoption.
670684

671685

672686
A new standard library module instead of builtins
673687
-------------------------------------------------
674688

675689
This would eliminate the third-party dependency problem, but still leaves the explicit import
676690
usability cost. In addition, there's no obvious place to add it to the stdlib *other than* in
677-
builtins. Two functions likely isn't worth the cost of a new top-level module. Besides, since
691+
builtins. Two functions likely aren't worth the cost of a new top-level module. Besides, since
678692
``__all__`` is in a sense built into Python, these functions should be built in too.
679693

680694

@@ -690,13 +704,17 @@ Open Issues
690704
visibility ergonomics.
691705
* Should ``private()`` support a function call form, for symmetry? ``atpublic`` does not provide
692706
one and no need for it has ever been demonstrated or requested.
707+
* Should ``public()`` and ``private()`` diagnose being called outside module scope? Neither
708+
inspects its calling scope today, so ``@public`` on a method silently adds the method's name to
709+
the module's ``__all__``. Raising an exception would be friendlier, at the cost of a scope check
710+
on every call, which bears on :ref:`pep-844-performance`.
693711
* Should the standard library itself adopt these decorators, and if so on what schedule? This
694712
question is entangled with :ref:`pep-844-performance` and should be settled with startup
695713
measurements in hand. Also, as with all new capabilities (such as lazy imports), Python's policy
696714
is generally not to wholesale update the stdlib to embrace the new functionality. These new
697715
functions can be utilized opportunistically in modules where the most benefit can be gained, or
698716
when a module undergoes substantial rewrite.
699-
* Import time benchmarks for a C implementation are outstanding.
717+
* Import-time benchmarks for a C implementation are outstanding.
700718

701719

702720
Acknowledgements

0 commit comments

Comments
 (0)