2

On a Linux environment, I have the following three files in a folder named utils:

  1. An empty file __init__.py

  2. A file named mymain.py with the following content:

    from mytool import foo
    
  3. A file mytool.py with the following content:

    from subprocess import check_output
    
    def foo():
        print("Hello World")
    

Now I create a virtual environment with the following setup:

astroid==2.4.2
isort==5.7.0
lazy-object-proxy==1.4.3
packaging==20.9
pylint==2.6.0
pyparsing==2.4.7
six==1.15.0
toml==0.10.2
typed-ast==1.4.2
wrapt==1.12.1

Then I run the pylint check on that folder

pylint utils

or

pylint --disable=C,R,W utils  # only the error is of interest

it should return a score of 10.

When you now upgrade the version of astroid to 2.5.0 and run the check again, you get the error:

************* Module utils.mymain
utils/mymain.py:2:0: E0401: Unable to import 'mytool' (import-error)

Is the changed behavior because of the deprecated importlib methods, as pointed out HERE?

Interesting fact

If you remove the unused import statement in the file mytool.py, you also get the same import error even with astroid version 2.4.2

Why; How can an unused import possibly affect the outcome of a pylint check?

Hint: I have two questions.

wim
  • 338,267
  • 99
  • 616
  • 750
Alex
  • 41,580
  • 88
  • 260
  • 469

2 Answers2

2

The problem is not reproducible, so it's hard to say whether the astroid version and the unused import statement are at the heart of the issue, but we can try investigating other avenues in our quest to better understand what's going on here.


In a nutshell

Executing pylint --disable=C,R,W utils behaves as if utils is imported as a module, causing the absolute import from mytool import foo to fail.


In detail

I believe you expected Pylint to return the perfect score since executing python3 utils/mymain.py from the parent folder or python3 mymain.py from the utils folder work.

Alas, these methods of execution work because sys.path[0] is initialized to the path of the utils folder. Thus, the absolute import statement from mytool import foo succeeds, as the package mytool is found within the paths specified by sys.path.

However, when Pylint is executed, its __main__.py file is treated as the main module and the path of the utils folder is no longer included anywhere within sys.path and the absolute import fails.

Here are three different approaches that cause pylint --disable=C,R,W utils to return the perfect score:

  1. Edit the import statement to be relative, i.e. to from .mytool import foo. Note that this change warrants you to execute your script as a module from the parent folder (e.g. python3 -m utils.mymain).

  2. Add the utils folder to PYTHONPATH before executing the script or to sys.path before reaching the absolute import statement.

    However, if you do prefer this approach of using an absolute import, I would recommend rearranging the project structure to reflect a well-designed module and importing it properly (e.g. from <module>.utils.mytool import foo). Of course, this requires installing the module locally or manipulating the path to support the import.

  3. Try running with Python 2. This works without any need for changes, due to the deprecated behavior of implicit relative imports in Python 2.


Addressing your questions on the effect of the astroid module and the unused import statement on the outcome of a Pylint check:

  • Since Pylint depends on the astroid module to work, a bug in the astroid module could affect the outcome of a Pylint check in unexpected ways, including in the manner you described.

  • Since Pylint was executed on the entire utils folder, the unused import statement in utils/mytool.py could appear as a warning, but it shouldn't alter the outcome of a Pylint check in the manner you described (unless it triggers some unexpected behavior in a buggy astroid version).

Thus, a buggy astroid version, with or without an unused import statement, could yield unexpected results. However, as the problem isn't reproducible, it's hard to research what exactly is going on here.


An alternative explanation to this cryptic behavior, suggested by Pylint's official documentation, is an installed module of the same name as the one tested:

You should give Pylint the name of a Python package or module. Pylint will not import this package or module, though uses Python internals to locate them and as such is subject to the same rules and configuration. You should pay attention to your PYTHONPATH, since it is a common error to analyze an installed version of a module instead of the development version.

Yoel
  • 9,144
  • 7
  • 42
  • 57
  • Thanks for your reply, but you do NEITHER answer WHY the version of astroid affects the outcome of this setup NOR why the import statement has an effect on the outcome. You said yourself: " an unused import statement shouldn't affect the outcome of a Pylint check, nor should the astroid module". But that was exactly my question. You did not answer my question at all. – Alex Mar 14 '21 at 08:26
  • 1
    Please try reproducing the problem in a fresh virtual environment. I believe that you'll see that neither the _astroid_ version, nor the unused import statement affect the outcome of the _Pylint_ check, which shall fail on both occasions due to the absolute import statement. Then try the approaches I listed and see whether the issue is resolved. – Yoel Mar 14 '21 at 15:26
  • Yes, I cannot reproduce it anymore. It seems to have been a "messed up" virtualenv. But how can a virtualenv get messed up? How to check if a virtualenv is "Ok"? – Alex Mar 15 '21 at 09:16
  • 1
    It's hard to tell what exactly happened without a reproducible example or access to the problematic virtual environment. As I alluded to in my answer, the culprit could be an installed module called `mytool`. Specifically, it's possible that between the first execution of _Pylint_ (which worked) and the following ones (which failed), you've uninstalled a module called `mytool` with the function `foo`, removing it from the virtual environment and causing the absolute import to no longer work. – Yoel Mar 15 '21 at 10:24
  • I changed either the version of `astroid` or the import statement. I did not "by chance" uninstall some module between the trials! Something else must have been broken . But what? How to find out?? – Alex Mar 15 '21 at 10:42
  • I have found a virtualenv where I can reproduce this behavior. WITH the extra import in the file `mytool.py` `pylint` gives a score of 10, WITHOUT the import it gives the error. I did NOT change anything in between. How to find out, what is going on? – Alex Mar 16 '21 at 15:11
  • Great! My first priority would be to test whether the absolute import works when executing the script directly (i.e. without _Pylint_) in order to understand which of the two _Pylint_ behaviors is the correct one. As I explained in my answer, I don't expect the absolute import to work, but if it does, I would research why. Only then I would try debugging _Pylint_, for example by introducing `pdb` into its _\_\_init\_\_.py_ file (e.g. *lib/python3.8/site-packages/pylint/__init__.py* in the virtual environment). – Yoel Mar 16 '21 at 21:11
  • I have changed the code to import the module absolute (`from utils.mytool import foo`). Now `pylint` works, but running `python utils/mymain.py` gives an ModuleNotFoundError. – Alex Mar 17 '21 at 06:42
  • Now I am not sure how to best debug that code. I do not even know where I should set a breakpoint! – Alex Mar 17 '21 at 07:04
  • If you want the absolute import statement `from utils.mytool import foo` to work when executing the script from the parent folder, you can execute it as a module, i.e. with `python3 -m utils.mymain`. – Yoel Mar 17 '21 at 09:19
  • Yes that works. But how can I possibly debug `pylint` to see the real cause of the problems I have? – Alex Mar 17 '21 at 10:38
  • As I wrote in the comment above, you can introduce *pdb* (i.e. `import pdb; pdb.set_trace()`) into _Pylint_'s *\_\_init\_\_.py* file (e.g. *lib/python3.8/site-packages/pylint/\_\_init\_\_.py* in the virtual environment). However, why can't you simply circumvent this issue, rather then waste your time trying to debug it? In other words, why can't you use the first two approaches listed in my answer? – Yoel Mar 17 '21 at 14:22
  • Ok, I give up! I noticed something strange with a virtualenv, there IS definitely an error, a bug, which I am not able to investigate further. End of the story! – Alex Mar 17 '21 at 14:23
  • Well, I hope that my answer and the ensuing discussion were helpful nonetheless. If you somehow manage to generate a reproducible example, I'd like to play around and debug it on my side, so do share. – Yoel Mar 18 '21 at 07:36
  • Sure I will. I am actually trying right now. – Alex Mar 18 '21 at 07:41
  • Since the problem is indeed non-reproducible, would you consider cancelling your downvote and even upvoting this answer? Of course, this is just a friendly request and it's entirely up to you to. You don't need to reply to this comment and feel free to reject my request if you feel I wasn't helpful and that you gained nothing from the discussion. Thanks anyway - I enjoyed the challenge :-) – Yoel Mar 19 '21 at 09:11
  • Sure. I am always trying to be a nice person. Thanks for your detailed input. – Alex Mar 19 '21 at 12:15
-1

Try changing import statement

From:

from mytool import foo

To:

from .mytool import foo

The dot in the module name is used for relative module import (Ref Link Here).