export name (PEP 842)
Summary
This import hook makes export a soft keyword, so that it automatically adds to
__all__ the relevant names in the following cases:
export class ClassName ...
export def function_name ...
export name ... = ...
where these statements occur at the top level (i.e. not within a function or class definition). In some sense, it complements the from … export (PEP 843) import hook. In the next section, we demonstrate how we can combine these two import hooks.
This import hook was inspired by
PEP 842 (withdrawn)
which suggested the addition of export as a soft keyword
to be used in the following three cases:
export name ... = ...
export def function_name(): ...
export class ClassName(): ...
Note that PEP 842 suggests a lot more than what we wrote above:
It suggest the creation of an
__export__list.It suggest that using
exportother than in top-level statement should result in anExportError.It suggests the creation of a modified
__dir__.etc.
We will first start with an example that implements only the three cases we mentioned. Consider the following file:
# export_name_1.py
from math import pi
export PI = pi
export public = "public variable"
export def useful_fn():
print("This is a very useful function")
def private():
print("I want to be able to change my name.")
secret = "Ideas's code is a mess."
Let’s use our import hook to import this function using a standard Python interpreter.
> py
Python 3.11.9 ...
>>> from ideas.included.export_name import add_hook
>>> hook = add_hook()
>>> import export_name_1
>>> dir(export_name_1)
['PI', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'pi', 'private', 'public', 'secret', 'useful_fn']
Looking closely at the output, we can see that private, pi, and secret are not exposed, but
it is not easy to see. However, they are still available.
>>> export_name_1.secret
"Ideas's code is a mess."
And, __all__ only shows the names we want, so we could quickly determine
if it is safe to use a star-import.
>>> export_name_1.__all__
['PI', 'public', 'useful_fn']
A restricted dir
As we can see from the example above, when simply using dir it might be difficult to identify
which names are “public”. Presumably for this reason, PEP 842 suggests that using export
in a module should also result in creating a __dir__ function within this module so that
Python’s dir function can be restricted to only show the desired names.
We have implemented a version of this idea, available as an option, demonstrated below.
> py
Python 3.11.9 ...
>>> from ideas.included.export_name import add_hook
>>> hook = add_hook(public_dir=True) # optional argument
>>> import export_name_1
>>> dir(export_name_1)
['PI', 'public', 'useful_fn']
We can nonetheless still see all the available names that dir would have shown us before,
and then some …
>>> list(vars(export_name_1))
['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__file__', '__cached__', '__builtins__', '__all__', '__dir__', 'pi', 'PI', 'public', 'useful_fn', 'private', 'secret']
Instead of creating a __dir__ function within the module, we prefer to use a simple function
that we have written, which extracts the content of __all__ if it exists, otherwise it
gives us what dir would give us normally, but not always in the same order.
> py
Python 3.11.9 ...
>>> from ideas.utils import pdir
>>> pdir()
['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', 'pdir']
>>> pdir().sort() == dir().sort()
True
>>> from ideas.included.export_name import add_hook
>>> hook = add_hook()
>>> import export_name_1
>>> pdir(export_name_1)
['PI', 'public', 'useful_fn']
As we can see, with a simple utility function, like pdir, we do not use to create a special
__dir__ within a module.
Implementation
Let’s explore how this is implemented, like we did in the from … export (PEP 843) import hook.
> py
Python 3.11.9 (tags/v3.11.9:de54cf5, Apr 2 2024, 10:12:12) [MSC v.1938 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from ideas import transform
>>> from ideas.included.export_name import add_hook
>>> hook = add_hook()
>>> transform("export def test(): ...")
__all__ = globals().setdefault("__all__", [])
__all__ = list(__all__)
__all__.append('test')
def test(): ...
We would have a similar result with class instead of def.
The situation is slightly different for variables.
First, a proper declaration.
>>> transform("export name = ...")
__all__ = globals().setdefault("__all__", [])
__all__ = list(__all__)
__all__.append('name')
name = ...
However, if not assignment is done using an = sign,
no transformation takes place.
>>> transform("export name ...")
export name ...
The same occurs if an export keyword is not used at the top level.
>>> transform("export name ...")
export name ...
>>> source = '''
... def test():
... export name = 'Bob'
... '''
>>> transform(source)
def test():
export name = 'Bob'
This would clearly cause a syntax error if it were to be executed.
Finally, let us give a single additional example with the public_dir
option. However, we can’t simply use the transform function as
it only takes a source as an argument and we need to tell
our import hook other arguments to take into account.
However, we can import a file, defined as follows:
# export_name_2.py
export name = 'Bob'
Here’s a sample session.
> py
Python 3.11.9 ...
>>> from ideas.included.export_name import add_hook
>>> from ideas import current_state
>>> current_state.show_changes = True
>>> hook = add_hook(public_dir=True)
>>> import export_name_2
#========== Original source from C:\Users\Andre\github\ideas\docs_examples\export_name_2.py ====
# flake8: noqa
# export_name_2.py
export name = 'Bob'
#=== End of Original source from C:\Users\Andre\github\ideas\docs_examples\export_name_2.py ====
#========== Transformed source ====
__all__ = globals().setdefault('__all__', [])
__dir__ = lambda: __all__
# flake8: noqa
# export_name_2.py
__all__ = globals().setdefault("__all__", [])
__all__ = list(__all__)
__all__.append('name')
name = 'Bob'
#=== End of Transformed source ====
As we can see, at the top of the transformed source, a new __dir__ function
has been introduced.
We will see how to combine our limited implementation of both PEP 842 and PEP 843 in the next section, export as a keyword.