2

I'm doing some patching of import statements, and I need to know exactly what members are imported by from m import *. The documentation seems to indicate that when __all__ is absent, all members will be imported that do not begin with an underscore. Is this exactly correct under all circumstances? I know that inspect.getmembers(), dir(), and m.__dict__ all have slightly different logic, so I'm not completely sure which (if any) will provide the same list as import *.

Thom Smith
  • 13,916
  • 6
  • 45
  • 91
  • https://docs.python.org/2/tutorial/modules.html#more-on-modules "This imports all names except those beginning with an underscore (_). Note that in general the practice of importing * from a module or package is frowned upon, since it often causes poorly readable code. However, it is okay to use it to save typing in interactive sessions." – Alexander Feb 21 '19 at 03:22

3 Answers3

4

Let's take a look at what that from m import * statement does:

>>> dis.dis(compile('from m import *', '<module>', 'single'))
  1           0 LOAD_CONST               0 (0)
              2 LOAD_CONST               1 (('*',))
              4 IMPORT_NAME              0 (m)
              6 IMPORT_STAR
              8 LOAD_CONST               2 (None)
             10 RETURN_VALUE

The key here is that it actually invokes a dedicated opcode IMPORT_STAR, and this is implementation specific to the interpreter that will execute this code. This operator was originally specified in PEP-0221 but the implementation details specified is in the comments introduced by this specific commit.

In CPython, this is found in /Python/ceval.c (Python 3.7.2) and it in turns call import_all_from which shows the general logic on what that actually does inside the bytecode interpreter.

In PyPy, this is found in /pypy/interpreter/pyopcode.py, and again much like the C implementation it invokes the import_all_from function defined in RPython, which again has a similar logic but in a more familiar syntax for Python programmers.

In both the CPython and pypy implementation, if __all__ is presented as a list of names inside the imported module, all matching assignments will be added to the current local scope, including those names that are prefixed with an underscore (_). Otherwise, every assignment inside the module that do not start with an underscore will be added to the current local scope.

metatoaster
  • 17,419
  • 5
  • 55
  • 66
  • The key point seems to be that if `__all__` does not exist, then it grabs the keys from `m.__dict__` and calls `getattr()`. This is different from `getmembers()` or `dir()`, and even subtly different from `m.__dict__.items()`. – Thom Smith Feb 21 '19 at 04:22
0

I'm currently using the following function to get a list of names and calling getattr(m, name) on each name:

def public_members(module):
    try:
        return module.__all__  # If not iterable, imports will break.
    except AttributeError:
        return [name for name in dir(module) if not name.startswith('_')]
Thom Smith
  • 13,916
  • 6
  • 45
  • 91
0

This is probably the hackiest thing you'll see all day, but it might do the trick.

bound = globals().copy()
from module import *
for k, v in list( globals().items() ):
    if k not in bound or bound[ k ] != v:
        print( 'new', repr( k ), repr( v ) )
Michael Speer
  • 4,656
  • 2
  • 19
  • 10