Ignat Insarov
Ignat Insarov

Reputation: 4832

In python, "export" a custom-tailored object from a module

In JavaScript, specifically in node.js setting, one can spell module.exports = 13; in module.js, then x = import ("module.js"); elsewhere and have 13 assigned to x directly.

This saves some code when a module exports a single function, and I notice a lot of widely used packages (such as through2) make use of it.

Is there a way to do the same in Python? With some black magic, maybe?

I do have heard of a thing called loader that's, I guess, supposed to do some manipulations with a module before making it available. In particular, I think SaltStack makes use of something like that in salt.loader, but the code is too hard for me to follow. I imagine we could write a function similar to this:

def loader(module):
    m = __import__(module)
    return m["__exports__"]

— Then define __exports__ somewhere in a module we want to import and enjoy functionality very similar to JavaScript's module.exports mechanics. But unfortunately TypeError: 'module' object has no attribute '__getitem__' prevents us from doing that.

Upvotes: 3

Views: 6387

Answers (2)

Ignat Insarov
Ignat Insarov

Reputation: 4832

After some thinking I understood that, while we can't say m["__exports__"] due to module object's class not having __getitem__ method, we can still access some of the module's elements with "dot" notation: m.__exports__ works.

Another way: screen all module level names off with an underscore and assign the object to be exported to a variable named after the module, then from module import *.

loader.py:

def loader(module):
    m = __import__(module)
    return m.__exports__

exports.py:

def _f():
    return 13

_a = 31

exports = {"p6": _f, "p8": _a}

__exports__ = exports

Python 2.7:

>>> import loader
>>> e = loader.load ("exports")
>>> e
{'p8': 31, 'p6': <function _f at 0x7fb79d494cf8>}
>>> from exports import *
>>> exports
{'p8': 31, 'p6': <function _f at 0x7fb79d494cf8>}

Python 3:

>>> import loader
>>> e = loader.load ("exports")
>>> e
{'p6': <function _f at 0x7f88ae229ae8>, 'p8': 31}
>>> from exports import *
>>> exports
{'p6': <function _f at 0x7f88ae229ae8>, 'p8': 31}

In the first way proposed, I unfortunately cannot use __all__ in loader.load to filter only listed names from a module being loaded since __getitem__ is not defined for module object.

In the second way proposed I don't get so much control (in that a malicious module can export arbitrary names and manipulate my namespace) and flexibility (in that I cannot assign the module's exported object to arbitrary name anywhere in my code).

So, there is still a bit left to be desired here.

Upvotes: 1

gz.
gz.

Reputation: 6711

Python has importing built in to the language at a more basic level than Javascript does, almost all use cases are covered by a simple import statement.

For your example, all it really boils down to is:

from module import exports as x

So, there's not need to look for code savings by changing module.

The other part of the question is how, as a module author, would you restrict people to seeing only a single symbol.

Generally this is not required except to help users know what are public functions vs implementation details. Python has a few common idioms for this:

  • Any names that start with a leading underscore, such as _helper, are considered private. They can be accessed as normal, but the implication is you should not.
  • If a module level variable __all__ = [...] exists, only the strings it contains are considered public. The names must seperatedly be declared in the module.

As well as being documentation, both of these do affect one aspect of the module import:

from module import *

Using a star import is generally discouraged, but only public names will be brought in to the local namespace.

Upvotes: 2

Related Questions