6

The Google Style Guide for python states that one should: "Use imports for packages and modules only."

https://google.github.io/styleguide/pyguide.html#Imports

Is there a tool that flags violations of this suggestion?

Pylint does NOT do it. For example, following: Is there a tool to lint Python based on the Google style guide?

Creating a test.py the violates the guideline (exists is a function, not a module):

"""Test file for pylint"""
from os.path import exists

exists('/home')

Then, running pylint with the rc file does just fine:

$ pylint --rcfile=googlecl-pylint.rc -r n -s n  test.py
$ echo $?
0

Searching through the possible codes: http://pylint-messages.wikidot.com/all-codes, I don't see anything that looks like it would warn against this.

I have also not seen anything in pep8 or pyflakes that will catch this.

Job Evers
  • 4,077
  • 5
  • 20
  • 26
  • 4
    Who made Google authority on Python programming conventions? You should stick to the [PEP guidelines](https://www.python.org/dev/peps/pep-0008/#imports), those are what's up. – cs95 Jul 11 '17 at 22:59
  • I am also searching for this. Any luck, @jobevers? – Mitar Jul 28 '17 at 07:49
  • @mitar: none yet. – Job Evers Jul 28 '17 at 15:51
  • 2
    @coldspeed FWIW, I personally like this style in my code because I find it easier to tell where all the names are coming from, and I'm a believer in _readability first_. Of course, it's a supplement to the conventions of PEP 8, not in conflict with it. – Hatshepsut Nov 28 '18 at 23:42
  • @Hatshepsut Note, that comment was made roughly one year before I joined the company, this post may no longer represent my views or opinions. Thanks, though! – cs95 Nov 29 '18 at 00:27
  • Looks like there is also https://github.com/atollk/flake8-import-restrictions now for flake8 – happymacaron Nov 19 '22 at 21:47

1 Answers1

6

I made the following pylint plugin for this purpose:

import astroid
from pylint import checkers, interfaces
from pylint.checkers import utils


class ImportOnlyModulesChecked(checkers.BaseChecker):
  __implements__ = interfaces.IAstroidChecker

  name = 'import-only-modules'
  priority = -1
  msgs = {
    'W5521': (
      "Import \"%s\" from \"%s\" is not a module.",
      'import-only-modules',
      "Only modules should be imported.",
    ),
  }

  @utils.check_messages('import-only-modules')
  def visit_importfrom(self, node):
    try:
      imported_module = node.do_import_module(node.modname)
    except astroid.AstroidBuildingException:
      # Import errors should be checked elsewhere.
      return

    if node.level is None:
      modname = node.modname
    else:
      modname = '.' * node.level + node.modname

    for (name, alias) in node.names:
      # Wildcard imports should be checked elsewhere.
      if name == '*':
        continue

      try:
        imported_module.import_module(name, True)
        # Good, we could import "name" as a module relative to the "imported_module".
      except astroid.AstroidImportError:
        self.add_message(
          'import-only-modules',
          node=node,
          args=(name, modname),
        )
      except astroid.AstroidBuildingException:
        # Some other error.
        pass


def register(linter):
  linter.register_checker(ImportOnlyModulesChecked(linter))
Mitar
  • 6,756
  • 5
  • 54
  • 86