pylint-dev / pylint

It's not just a linter that annoys you!
https://pylint.readthedocs.io/en/latest/
GNU General Public License v2.0
5.25k stars 1.12k forks source link

E0602 (undefined-variable) - False positive #3298

Open arquolo opened 4 years ago

arquolo commented 4 years ago

Given:

> pylint --version
pylint 2.6.0
astroid 2.4.2
Python 3.8.5 (default, Sep  3 2020, 21:29:08) [MSC v.1916 64 bit (AMD64)]

> tree .
.
├──bug
│  ├── __init__.py
│  └── module.py
└──run_bug.py

with bug/__init__.py:

from .module import *

__all__ = module.__all__

and bug/module.py:

__all__ = ['func']

def func() -> None:
    print('works')

and run_bug.py:

from bug import func

func()

code works:

> python -c "import bug ; bug.func()"
works

and this also works:

> python run_bug.py
works

and bug is here:

> pylint bug run_bug --disable=C
************* Module bug
bug\__init__.py:3:10: E1101: Class 'module' has no '__all__' member (no-member)
bug\__init__.py:3:10: E0602: Undefined variable 'module' (undefined-variable)

Problem is that when * is imported from bug.module, module becomes imported to bug, but PyLint thinks that it doesn't. All I wanted is to delegate filling of __all__ to submodules, like in asyncio. Explanation is here.

jamesbraza commented 3 years ago

I believe I'm hitting this same bug. Seems like the problem lies with some combination of:


Minimal Repro

other_file:

__all__ = ["name"]

name = "foo"

main_file:

from .other_file import *

_ = name

This results in the following error message:

main_file.py: E0602: Undefined variable 'name' (undefined-variable)

Output of pylint --version:

pylint 2.6.0
astroid 2.4.2
Python 3.8.6 (default, Oct 27 2020, 11:18:42)
[Clang 12.0.0 (clang-1200.0.32.27)]
hippo91 commented 3 years ago

@jamesbraza i'm trying to reproduce your case but i end up with ImportError: attempted relative import with no known parent package when running python3 main.py. The file structure is:

tree . 
.
├── main_file.py
└── other_file.py

0 directories, 2 files
jamesbraza commented 3 years ago

Hi @hippo91 sorry it looks like I was invoking my minimal repro differently. The error you're getting lies in the concepts from this stack overflow question: Relative imports in Python 3.

There are multiple options at invocations for directly with python3, including:

undef_var_bug
├── main_file.py
└── other_file.py

Update import in from undef_var_bug.other_file import *, and invoke via python3 -m undef_var_bug.main_file.

Let me know if neither of those work for you.

hippo91 commented 3 years ago

@jamesbraza , i tested with the first solution. So i have the following code structure:

tree bug_pylint_3298
bug_pylint_3298
├── main_file.py
└── other_file

with:

cat bug_pylint_3298/main_file.py
from other_file import *

_ = name

and

cat bug_pylint_3298/other_file.py
__all__ = ["name"]

name = "foo"

Then linting is ok:

pylint bug_pylint_3298
************* Module bug_pylint_3298.main_file
bug_pylint_3298/main_file.py:1:0: C0114: Missing module docstring (missing-module-docstring)
bug_pylint_3298/main_file.py:1:0: W0401: Wildcard import other_file (wildcard-import)
************* Module bug_pylint_3298.other_file
bug_pylint_3298/other_file.py:1:0: C0114: Missing module docstring (missing-module-docstring)
bug_pylint_3298/other_file.py:3:0: C0103: Constant name "name" doesn't conform to UPPER_CASE naming style (invalid-name)
pylint --version
pylint 2.6.1-dev1
astroid 2.5.0
Python 3.7.9 (default, Nov 26 2020, 08:29:18) 
[GCC 8.3.0]

The false positive undefined-variable message emission doesn't occur. Do you agree?

arquolo commented 3 years ago

@hippo91 No, undefined-variable message does occur, you invoke it wrong. I have updated issue

jamesbraza commented 3 years ago

@hippo91 so I copied what you'd set up, and I figured out the problem, I don't believe your code actually runs. I know because when I ran it via python -m bug_pylint_3298.main_file, the result was: ModuleNotFoundError: No module named 'other_file'

Your main_file.py begins with from other_file:

from other_file import *

However, it should be:

from bug_pylint_3298.other_file import *

And now the module executes as intended. And then when you invoke pylint, the bug surfaces:

(venv) ➜  bananas_dir pylint bug_pylint_3298
************* Module bug_pylint_3298.main_file
bug_pylint_3298/main_file.py:1:0: C0114: Missing module docstring (missing-module-docstring)
bug_pylint_3298/main_file.py:1:0: E0401: Unable to import 'bug_pylint_3298.other_file' (import-error)
bug_pylint_3298/main_file.py:1:0: W0401: Wildcard import bug_pylint_3298.other_file (wildcard-import)
bug_pylint_3298/main_file.py:3:4: E0602: Undefined variable 'name' (undefined-variable)
************* Module bug_pylint_3298.other_file
bug_pylint_3298/other_file.py:1:0: C0114: Missing module docstring (missing-module-docstring)
bug_pylint_3298/other_file.py:3:0: C0103: Constant name "name" doesn't conform to UPPER_CASE naming style (invalid-name)

To fix it in this case, one can add an __init__.py to the bug_pylint_3298 directory, and it seems the error disappears.

(venv) ➜  bananas_dir pylint bug_pylint_3298
************* Module bug_pylint_3298.main_file
bug_pylint_3298/main_file.py:1:0: C0114: Missing module docstring (missing-module-docstring)
bug_pylint_3298/main_file.py:1:0: W0401: Wildcard import bug_pylint_3298.other_file (wildcard-import)
************* Module bug_pylint_3298.other_file
bug_pylint_3298/other_file.py:1:0: C0114: Missing module docstring (missing-module-docstring)
bug_pylint_3298/other_file.py:3:0: C0103: Constant name "name" doesn't conform to UPPER_CASE naming style (invalid-name)

Seems like pylint:

  1. Fails to realize the code actually runs
  2. Then reports import-error
  3. Finally reports undefined-variable, since it can't seem to parse the wildcard import (hence the preceding import-error)
jamesbraza commented 3 years ago

However, @arquolo seems to have changed the issue to be from E0602 (undefined-variable) to E1101 (no-member) w.r.t. module.__all__, which now makes my contributions here being sort of off-topic.

@arquolo I think your problem may be in the fact that python -c is different from just invoking pylint.

Invoking via python -c runs the input as if it's a __main__, which is why this works. However, if you were to try and invoke in a plain way, you can see Python can't actually run:

(venv) ➜  bananas_dir tree bug
bug
├── __init__.py
└── module.py

(venv) ➜  bananas_dir python -m bug
/path/to/bin/python: No module named bug.__main__; 'bug' is a package and cannot be directly executed

This may be why pylint can't properly parse the wildcard import... somewhere behind the scenes the real problem may be that the module can't be run. Just my guess, but I am not sure.

arquolo commented 3 years ago

@jamesbraza I have updated issue, and yet it's still E0602 (it was 2 am here, I misread the log). It doesn't depend whether module is imported in python -c, or in a plain way in python script, it still works. I'm not trying to run a package, but import it. Problem is not with star import, but with star import from subpackage, what imports subpackage too, and Pylint fails with last.

hippo91 commented 3 years ago

@arquolo and @jamesbraza thanks for your remarks. I'm now able to reproduce the bug. Here is attached a reproducer. I can confirm that adding an empty __init__.py file makes the bug disappear. That's why this issue may be linked to #3944. bug_pylint_3298.tar.gz

NeilGirdhar commented 3 years ago

I've been running into the same problem. It is as simple as

from .x import y
del x  # undefined-error!

in any file inside a package. Pylint just needs to do what Python does, which is to add an implicit import x.

stdedos commented 1 year ago

Is this similar to

src/matplotlib-stubs/__init__.pyi:37:17: E0602: Undefined variable 'PathLike' (undefined-variable)

and https://github.com/hoel-bagard/matplotlib-stubs/blob/f24bcb7808881bf5a1dd7271ce50b0ad0fb0051f/src/matplotlib-stubs/__init__.pyi#L36-L38 ?