Reputation: 15379
In Python, it is easy to check if an object is a module with isinstance(obj, types.ModuleType)
. We can also programmatically generate modules. I am however interested in going the other way around - generating code that would have created an import resulting in the module being added the globals/locals namespace. Basically a function assemble_import
like this:
def assemble_import(module: types.ModuleType, name: str) -> str:
pass
Roughly satisfying following conditions:
import statistics
assert assemble_import(statistics, 'statistics') = 'import statistics'
from os import path
assert assemble_import(path, 'path') = 'from os import path'
import collections.abc
abc = collections.abc
assert assemble_import(abc, 'abc') = 'from collections import abc'
import abc as xyz
assert assemble_import(xyz, 'xyz') = 'import abc as xyz'
I would not want it to use the abstract syntax tree, but rather the module object itself. What I have tried to so far:
module.__spec__
- it returns ModuleSpec
with the name attributemodule.__package__
- not sure why but is empty most of the timemodule.__loader__
- usually SourceFileLoader
, also has the name attributethe problem with the name attribute is that it is 'posixpath' for os.path
, while from os import posixpath
clearly does not work. Also, I do not see how to get the parent module (os
in the os.path
example).
Is achieving a workable (though not necessarily a production-ready/bulletproof) solution possible in Python? In other words, is the information about packages structure needed to recreate the imports code preserved/accessible?
Upvotes: 1
Views: 470
Reputation: 280291
You can, and it's quite straightforward, but os.path
will be a little weird:
def assemble_import(module, name):
return 'import {} as {}'.format(module.__name__, name)
os.path
is weird because it's a platform-dependent alias for either the ntpath
module or the posixpath
module. This function will use the module's actual name, either ntpath
or posixpath
. If you want to treat os.path
as os.path
, you can special-case it, though it might not be the best design choice.
For actual package submodules, like collections.abc
, this function will treat them as submodules of their containing package:
>>> assemble_import(collections.abc, 'abc')
'import collections.abc as abc'
but for os.path
, this will give you output like
>>> assemble_import(os.path, 'path')
'import posixpath as path'
If you want imports that look a bit more like what a human would normally write, you can add some logic to the function:
def assemble_import(module, name):
pname, _, mname = module.__name__.rpartition('.')
if pname:
statement = 'from {} import {}'.format(pname, mname)
else:
statement = 'import ' + mname
if mname != name:
statement += ' as ' + name
return statement
Upvotes: 1