Reputation: 32071
I'm writing a pylint checker and I need to differentiate between an import that is a sibling import of a package and an import of a function or class.
Example of sibling import:
from . import sibling_package
Example of a function import:
from numpy import array
The latter example I want to flag, while the former I want to allow, so I need to be able to tell the difference between the two.
I'm currently using:
modspec = importlib.util.find_spec('numpy', 'array')
That returns a ModuleSpec
, but I'm unclear how I can get to the goal of identifying the import array
as a module vs. a function/class. In this example it is a function import, and thus should be flagged.
Upvotes: 2
Views: 89
Reputation: 1121804
This is not something you can easily detect just from the import line. Python is highly dynamic and not until runtime can you know what type of object the import resolves to. The module spec can't tell you this information, because an attribute on a module can resolve to anything (including another module).
The alternatives that I can see are:
to do the actual import, then test the object type.
This is not without risk, imports can have side effects. Importing a module includes executing the top level statements. Those side effects can be mild, like replacing one object with another when a dependency is not met (try: from itertools import zip_longest
, except ImportError: from itertools import izip_longest as ziplongest
is a trivial Python 2 vs. Python 3 dependency check), but potentially an import could make filesystem changes!
importing is also going to slow down the check. Importing a module like numpy
or pandas
can pull in å significant number of additional modules. You generally want to keep linting fast, or otherwise developers will tend not to bother and skip linting altogether.
Keep a list of known modules. For those modules you know about, complain if they are importing names from the module and not the module itself. This is fast, and will catch the majority of common cases. You can augment the list with what you can glean from the filesystem around the module being linted. Put differently, aim for fast and good enough and accept a few misses for new imports.
Only complain if imported names are called directly. Register all the names that are imported, and if the AST contains a Call
node for that name then you know they imported a function or class. from foo import bar
, then later on spam = bar('baz')
is a clear indicator that bar
is not a module.
Upvotes: 2