3

Let's consider the following example:

def dec(arg):
    def wrapper(m):
        def result(self):
            print(arg)
            m(self)
        return result
    return wrapper


class X:

    x = [7]

    @dec(x)
    def f(self):
        print('hello from f')

    @dec([i + 5 for i in x])
    def g(self):
        print('hello from g')


X().f()
X().g()

Code works fine under my python3.6 installation, but pylint complains on undefined-variable x in decorator's parameter of g method. Why this happen? Is this code not a good idea to implement this way? If so - any alternatives?

pt12lol
  • 2,332
  • 1
  • 22
  • 48
  • 1
    Seems to be a bug in pylint, there's also [this issue](https://github.com/PyCQA/pylint/issues/1976). – a_guest Apr 17 '20 at 12:52
  • 1
    I would just disable the warning: `@dec(...) # pylint: disable=undefined-variable` – chepner Apr 17 '20 at 12:56
  • 1
    There's an old bug in Pylint "undefined-variable when using class attribute in listcomp in decorator" back from 2015 that looks like it was never fixed: https://github.com/PyCQA/pylint/issues/511 – anjsimmo Apr 18 '20 at 03:49
  • @anjsimmo yes - that's exactly the stuff – pt12lol Apr 18 '20 at 09:30
  • Using list comprehensions in a class body tends to be a bad idea anyway due to a [completely different scope issue](https://stackoverflow.com/questions/13905741/accessing-class-variables-from-a-list-comprehension-in-the-class-definition), but that's not the issue you're hitting. – user2357112 Apr 25 '20 at 04:58

1 Answers1

3

Short Answer: It's a bug in Pylint 2.4.4 (and older). I've submitted a patch, which has been merged into master, and will be included in the next release of Pylint.

Details (for the curious):

When Python parses the code into Abstract Syntax Tree (AST) nodes, decorator nodes (e.g @dec...) are attached to the decorator_list of the node for the function definition (e.g. def g...):

    stmt = FunctionDef(identifier name, arguments args,
                       stmt* body, expr* decorator_list, expr? returns,
                       string? type_comment)

Pylint had a special case for function arguments (args), but forgot to check the decorator_list and the returns annotation. This was causing Pylint to incorrectly treat the decorators as if they were inside the function, and thus in function scope rather than class scope.

Workaround:

As a static code checker, Pylint can only go so far. It currently has 500 open issues, so expect a few false positives. For now, you can follow @chepner's suggestion to disable the Pylint warning, just for that line:

    @dec([i + 5 for i in x]) # pylint: disable=undefined-variable

But I want it now! (for those who want a 10/10 code rating without cheating)

git clone https://github.com/PyCQA/astroid.git
git clone https://github.com/PyCQA/pylint.git
pip install ./astroid/
pip install ./pylint/
pylint --version

pylint 2.5.0-dev1
astroid 2.4.0

With the development version of Pylint (and Astroid, the library Pylint uses for parsing), the code in the question shouldn't give any undefined-variable complaints (though it will give some style warnings). If this ever changes, please reopen the bug report!

anjsimmo
  • 704
  • 5
  • 18