From 5f71d931b5a8a7bc60d9308b8772305188448a4a Mon Sep 17 00:00:00 2001
From: Andrea Guarino Nested functions and lambdas can reference variables defined in enclosing scopes. This can create tricky bugs when the variable and the function
-are defined in a loop. If the function is called after the loop, it will see the variables last value instead of seeing the values corresponding to
-the iteration where the function was defined. This rule raises an issue when a nested function or lambda references a variable defined in an enclosing loop.
Capturing loop variables might work for some time but:
+One solution is to add a parameter to the function/lambda and use the previously captured variable as its default value. Default values are only +executed once, when the function is defined, which means that the parameter's value will remain the same even when the variable is reassigned in +following iterations.
+Another solution is to pass the variable as an argument to the function/lambda when it is called.
+This rule raises an issue when a function or lambda references a variable defined in an enclosing loop.
def run(): @@ -12,6 +22,20 @@Noncompliant Code Example
def func(): return i # Noncompliant mylist.append(func) + +def example_of_api_change(): + """" + Passing loop variable as default values also makes sure that the code is future-proof. + For example the following code will work as intended with python 2 but not python 3. + Why? because "map" behavior changed. It now returns an iterator and only executes + the lambda when required. The same is true for other functions such as "filter". + """ + lst = [] + for i in range(5): + lst.append(map(lambda x: x + i, range(3))) # Noncompliant + for sublist in lst: + # prints [4, 5, 6] x 4 with python 3, with python 2 it prints [0, 1, 2], [1, 2, 3], ... + print(list(sublist))
@@ -23,21 +47,33 @@Compliant Solution
def func(i=i): # same for nested functions return i mylist.append(func) + +def example_of_api_change(): + """" + This will work for both python 2 and python 3. + """ + lst = [] + for i in range(5): + lst.append(map(lambda x, value=i: x + value, range(3))) # Passing "i" as a default value + for sublist in lst: + print(list(sublist))
No issue will be raised if the function or lambda is only called in the same loop.
+No issue will be raised if the function or lambda is directly called in the same loop. This still makes the design difficult to understand but it +is less error prone.
def function_called_in_loop():
for i in range(10):
- print((lambda param: param * i)(42))
+ print((lambda param: param * i)(42)) # Calling the lambda directly
def func(param):
return param * i
- print(func(42))
+ print(func(42)) # Calling "func" directly
This rule is deprecated; use {rule:python:S5722} instead.
diff --git a/python-checks/src/main/resources/org/sonar/l10n/py/rules/python/S2733.json b/python-checks/src/main/resources/org/sonar/l10n/py/rules/python/S2733.json index 1501d5da9a..2ac2f821a5 100644 --- a/python-checks/src/main/resources/org/sonar/l10n/py/rules/python/S2733.json +++ b/python-checks/src/main/resources/org/sonar/l10n/py/rules/python/S2733.json @@ -1,7 +1,7 @@ { "title": "\"__exit__\" should accept type, value, and traceback arguments", "type": "BUG", - "status": "ready", + "status": "deprecated", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" diff --git a/python-checks/src/main/resources/org/sonar/l10n/py/rules/python/S5655.html b/python-checks/src/main/resources/org/sonar/l10n/py/rules/python/S5655.html index 6f2dae2f9c..6d0cc72e54 100644 --- a/python-checks/src/main/resources/org/sonar/l10n/py/rules/python/S5655.html +++ b/python-checks/src/main/resources/org/sonar/l10n/py/rules/python/S5655.html @@ -1,16 +1,31 @@ -Python does not check the type of arguments provided to functions. However builtin functions and methods expect a specific type for each parameter. -Providing an argument of the wrong type will make your program fail.
-This rule raises an issue when a builtin function is called with an argument of the wrong type.
+The CPython interpreter does not check arguments type when functions are called. However a function can express the type it expects for each
+argument in its documentation or by using Type Hints. Calling such a function with an argument
+of a different type can easily create a bug. Even if it works right now it can fail later when APIs evolve or when type checks are added (ex: with
+isinstance).
This rule raises an issue when a function or method is called with an argument of a different type than the one described in its type annotations. +It also checks argument types for builtin functions.
-round("42.3") # Noncompliant
+def func(var: str):
+ pass
+
+func(42) # Noncompliant
+
+len(1) # Noncompliant
-round(42.3)
+def func(var: str):
+ pass
+
+func("42")
+
+len("1")
The walrus operator := (also known as "assignment expression") should be used
with caution as it can easily make code more difficult to understand and thus maintain. In such case it is advised to refactor the code and use an
assignment statement (i.e. =) instead.
This rule raises an issue raises an issue when the walrus operator is used in a way which makes the code confusing, as described in This rule raises an issue when the walrus operator is used in a way which makes the code confusing, as described in PEP 572.
diff --git a/sonarpedia.json b/sonarpedia.json
index 6c06175087..5bc91f0824 100644
--- a/sonarpedia.json
+++ b/sonarpedia.json
@@ -3,7 +3,7 @@
"languages": [
"PY"
],
- "latest-update": "2020-03-24T08:46:39.066Z",
+ "latest-update": "2020-04-17T11:28:06.007445200Z",
"options": {
"no-language-in-filenames": true,
"preserve-filenames": true