New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Assignment Expression Reports superfluous-parens False Positive #3249
Comments
Thanks for the report! |
Ah, it seems this issue is more general than I first thought. The negation has nothing to do with it. Example that also causes if (foo := True):
print(foo) |
I would say that example is a legit case where we do want to raise this warning. In the example below, I believe the expected behaviour would be to raise a superfluous warning for 2, a syntax error for 3, and no warning for 1 & 4 # 1: Legal python, not superfluous
if foo := True:
print(foo)
# 2: Legal python, superfluous
if (foo := True):
print(foo)
# 3: Illegal python, not superfluous
if not foo := True:
print(foo)
# 4: Legal python, "superfluous"
if not (foo := True):
print(foo) |
@huwcbjones Ah, thanks for going through some examples. So, in a conditional, is there no correct way to negate a assignment expression (regarding examples 3 and 4)? |
No worries, I came across this issue at work when I was trying to pylint some negated assignments and wondering why it was getting rejected! So from what I can tell, you can definitely negate an assignment. In both 3 & 4, the print statement will not be executed, but after the conditional, The "correct" way or, maybe more accurately, the only way to negate an assignment is to use wrap the assignment in brackets (as done in 4) however this leads to pylint complaining. But 3 throws a syntax error due to the difference in operator bindings (according to #3278). So to answer your original question, yes this is a correct way to negate an assignment, however pylint should not report this - it is a false positive (as in the OP/title). Coincidentally, I've found that the pattern of negating the assignment and early returning leads to cleaner. if not (data := find_something()):
log.warning("Could not find x")
return
do_something_with_data(data) Rather than data = find_something()
if not data:
log.warning("Could not find x")
return
do_something_with_data(data) |
This also applies to assert (ret := str(42))
|
A couple more things. These apply to all conditionals, so while loops and assigned comparisons also give a false positive: if not (a := True):
pass
while not (a := True):
pass
cond = not (a := True) Meanwhile, in addition to 'not' and 'assert', the 'in' keyword also triggers a false positive. And in all cases where one of these keywords is present, the false positive is avoided when the assignment expression contains a comma within a pair of brackets or curly braces. I wrote the test cases for the 'in' keyword, then realized it was more general and didn't want to rewrite them. In other words: # False positive (str, tuple)
A = [int(n) for n in (nums := '123')]
B = [int(n) for n in (nums := ('1', '2', '3'))]
# No false positive (list, set, dict)
C = [int(n) for n in (nums := ['1', '2', '3'])]
D = [int(n) for n in (nums := {'1', '2', '3'})]
E = [int(n) for n in (nums := {'1': 1, '2': 2, '3': 3})]
# False positive (brackets only)
F = [int(n) for n in (nums := ['1'])]
G = [int(n) for n in (nums := {'1'})]
H = [int(n) for n in (nums := {'1': 1})]
I = [int(n) for n in (nums := ('1'))]
J = [int(n) for n in (nums := ('1', ))]
# No false positive (brackets plus comma)
K = [int(n) for n in (nums := ['1', ])]
L = [int(n) for n in (nums := {'1', })]
M = [int(n) for n in (nums := {'1': 1, })]
# False positives (comma outside brackets)
N = [int(next(iter(n))) for n in (nums := (['1'], ['2'], ['3']))]
O = [int(next(iter(n))) for n in (nums := ({'1'}, {'2'}, {'3'}))]
P = [int(next(iter(n))) for n in (nums := ({'1': 1}, {'2': 2}, {'3': 3}))]
# False positives (aliasing)
NUMS_LIST = ['1', '2', '3']
NUMS_SET = {'1', '2', '3'}
NUMS_DICT = {'1': 1, '2': 2, '3': 3}
Q = [int(n) for n in (nums := NUMS_LIST)]
R = [int(n) for n in (nums := NUMS_SET)]
S = [int(n) for n in (nums := NUMS_DICT)]
T = [int(n) for n in (nums := [*NUMS_LIST])]
U = [int(n) for n in (nums := NUMS_LIST[:])]
# 2D arrays with numpy (since they allow commas within indexing)
import numpy
ARRAY_2D = numpy.zeroes((3, 3), dtype=bool)
V = not (vals := ARRAY_2D[:, :]) # No false positive
X = not (vals := ARRAY_2D[2, 2]) # No false positive
W = not (vals := ARRAY_2D[::]) # False positive
Y = not (vals := ARRAY_2D[2:2]) # False positive
Z = not (just_want_to_finish_the_alphabet := True) # False positive |
I came across this without a walrus operator:
produces "Unnecessary parens after 'assert' keyword (superfluous-parens)" |
@nedbat , that is because your sample is a case of superfluous params in Python 3.6.8, 3.7.2, 3.8.0 (all I had installed locally). See output below:
A similar case is shown below. BODMAS (whatever flavour you use) says that multiplication binds tighter than addition, so the brackets are redundant (superfluous). Additionally this issue is about the assignment expression, which does not feature in your complaint. |
@huwcbjones No, it's not about BODMAS. It's about the paren immediately following the assert keyword. In these lines, only the last one is given the violation:
|
Ah, yes I see what you mean now. Just to make sure we're on the same page, all 5 of those statements should raise However, my earlier point still stands, this is a different bug to the one reported in this thread as it does contain the assignment operator. |
No, none of them should. The violation in question here is about writing assert as if it were a function:
None of these examples do that. Pylint should not and does not complain about over-parenthesizing expressions. |
The same issue occurs on yield statements:
|
I can also confirm tried Pylint 2.6.0 and 2.7.0 and in both cases was still able to get the false flag. Same case as @RaenonX
|
Under the version listed below, this occasionally exists.
False positive / warning still appearExample 1if not (result := validate_conditions(conditions)):
raise ConditionValidationFailedError(result) Example 2if not (lang_asset := self._assets.get(lang_code)):
raise LanguageAssetNotFoundError(lang_code) Example 3# REMOVE: not with walrus https://github.com/PyCQA/pylint/issues/3249
if not (lang_entry := lang_asset.get(label)):
if on_not_found is THROW_ERROR_ON_FAIL:
raise TextLabelNotFoundError(label)
return on_not_found True positive / warning disappearsExample 1if not (hit_label := self.assigned_hit_label):
return None Example 2if not (hit_attr := asset_manager.asset_hit_attr.get_data_by_id(hit_label)):
return None Example 3if not (enemy_param_id := data[variation_field]):
continue Example 4if not (hit_attr_data := self._asset_hit_attr.get_data_by_id(hit_label)):
continue I have no idea why some warnings are gone while the others are not, so I am throwing all I have here. Hopefully this helps. |
Thanks, for the additional details and test cases. I pinned the issue. |
Another example where there is a false positive: [el
for lst in list_of_lists
for el in (lst if isinstance(lst, (list, tuple)) else [lst])] The |
Update: The examples I provided above still has the issue. Version info:
|
This fixes the false positives identified in pylint-dev#2818, pylint-dev#3249, pylint-dev#3608 & pylint-dev#4346 All false positives reported fell under keywords before walrus operator or if-keyword within generators/comprehension. This closes pylint-dev#2818, closes pylint-dev#3429, closes pylint-dev#3608, closes pylint-dev#4346
This fixes the false positives identified in pylint-dev#2818, pylint-dev#3249, pylint-dev#3608 & pylint-dev#4346 All false positives reported fell under keywords before walrus operator or if-keyword within generators/comprehension. This closes pylint-dev#2818, closes pylint-dev#3429, closes pylint-dev#3608, closes pylint-dev#4346
This fixes the false positives identified in pylint-dev#2818, pylint-dev#3249, pylint-dev#3608 & pylint-dev#4346 All false positives reported fell under keywords before walrus operator or if-keyword within generators/comprehension. This closes pylint-dev#2818, closes pylint-dev#3429, closes pylint-dev#3608, closes pylint-dev#4346
* Split functional tests for ``superfluous-parents`` * Fix false positives for superfluous-parens This fixes the false positives identified in #2818, #3249, #3608 & #4346 All false positives reported fell under keywords before walrus operator or if-keyword within generators/comprehension. This closes #2818, closes #3429, closes #3608, closes #4346 * Move the superfluous functional tests to functional/s/super Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
@Pierre-Sassoulas For some reason this did not auto-close. I think #4792 has all test cases not covered by #4784, so I think this can be closed? |
An
if
statement that tests a negated (not
) parenthesized Assignment Expression without any other operators (See "Non-issues" section below) is reported by Pylint to have superfluous parentheses.I believe this is a false positive. Removing the parentheses is invalid syntax.
Steps to reproduce
not
) Assignment Expression, such as:Current behavior
Pylint reports
superfluous-parens:Unnecessary parens after 'not' keyword
.Expected behavior
Pylint should not report about this syntax.
Removing the parentheses is a syntax error:
SyntaxError: cannot use named assignment with operator
.pylint --version output
Non-issues
If the negated assignment-expression
if
statement contains another binary operator, no Pylint report is generated about it. (Or at least, that seems to be the only difference.)For example, this slightly-modified snippet from What’s New In Python 3.8 does not cause Pylint to report:
The text was updated successfully, but these errors were encountered: