1- < p > This rule raises an issue when several elements are added to the same list through consecutive < code > append()</ code > calls, either as a sequence of
2- statements or through a for-loop.</ p >
1+ < p > This rule raises an issue when a for-loop adds every element of a collection to a list through < code > append()</ code > .</ p >
32< h2 > Why is this an issue?</ h2 >
4- < p > Adding elements to a list one at a time is unnecessary when the < code > extend()</ code > method exists. This happens with a sequence of
5- < code > append()</ code > statements:</ p >
6- < pre >
7- my_list.append(first)
8- my_list.append(second)
9- my_list.append(third)
10- </ pre >
11- < p > It also happens when a for-loop appends every element of another collection, which is the same sequence of calls spread over an iteration:</ p >
3+ < p > Adding elements to a list one at a time is unnecessary when the < code > extend()</ code > method exists. This happens when a for-loop appends every
4+ element of another collection:</ p >
125< pre >
136for element in other_collection:
147 my_list.append(element)
158</ pre >
16- < p > The < code > extend()</ code > method combines all additions into a single call and avoids the overhead of repeated < code > append()</ code >
17- invocations:</ p >
9+ < p > The < code > extend()</ code > method adds all elements in a single call and avoids the overhead of repeated < code > append()</ code > invocations:</ p >
1810< pre >
19- my_list.extend([first, second, third])
2011my_list.extend(other_collection)
2112</ pre >
2213< h3 > What is the potential impact?</ h3 >
23- < p > Grouping the additions makes the intent explicit: the reader sees one operation that grows the list instead of a series of independent statements .
14+ < p > Replacing the loop makes the intent explicit: the reader sees one operation that grows the list instead of an iteration whose body has to be read .
2415It also removes redundant call overhead, since the attribute lookup and the method call are resolved once instead of once per element. In hot code or
2516when the source collection is large, the difference becomes measurable.</ p >
2617< h3 > Exceptions</ h3 >
27- < p > No issue is raised when other statements are interleaved between the < code > append()</ code > calls, or when the body of the loop does more than
28- append the loop variable, for example when it filters or transforms the elements:</ p >
18+ < p > No issue is raised when the body of the loop does more than append the loop variable, for example when it filters or transforms the elements:</ p >
2919< pre >
3020for element in other_collection:
3121 if element.is_valid():
3222 my_list.append(element)
3323</ pre >
3424< h2 > How to fix it</ h2 >
35- < p > Group the appended elements into a single < code > extend()</ code > call. For consecutive statements, pass the elements as a list literal. For a loop,
36- pass the iterated collection directly, as < code > extend() </ code > accepts any iterable.</ p >
25+ < p > Replace the loop with a single < code > extend()</ code > call and pass the iterated collection directly, as < code > extend() </ code > accepts any
26+ iterable.</ p >
3727< h3 > Code examples</ h3 >
3828< h4 > Noncompliant code example</ h4 >
3929< pre data-diff-id ="1 " data-diff-type ="noncompliant ">
40- def build_command(base_command, path):
41- command = list(base_command)
42- command.append("--archive") # Noncompliant
43- command.append("--verbose")
44- command.append(path)
45- return command
46- </ pre >
47- < h4 > Compliant solution</ h4 >
48- < pre data-diff-id ="1 " data-diff-type ="compliant ">
49- def build_command(base_command, path):
50- command = list(base_command)
51- command.extend(["--archive", "--verbose", path])
52- return command
53- </ pre >
54- < h4 > Noncompliant code example</ h4 >
55- < pre data-diff-id ="2 " data-diff-type ="noncompliant ">
5630def collect_results(results, new_results):
5731 for result in new_results:
5832 results.append(result) # Noncompliant
5933 return results
6034</ pre >
6135< h4 > Compliant solution</ h4 >
62- < pre data-diff-id ="2 " data-diff-type ="compliant ">
36+ < pre data-diff-id ="1 " data-diff-type ="compliant ">
6337def collect_results(results, new_results):
6438 results.extend(new_results)
6539 return results
6640</ pre >
6741< h3 > How does this work?</ h3 >
68- < p > < code > extend()</ code > iterates over its argument and appends every element to the list, so the result is exactly the same as the individual calls.
69- The augmented assignment < code > my_list += [first, second, third]</ code > is equivalent to < code > extend()</ code > and can be used instead when it reads
70- better.</ p >
42+ < p > < code > extend()</ code > iterates over its argument and appends every element to the list, so the result is exactly the same as the loop. The
43+ augmented assignment < code > my_list += other_collection</ code > is equivalent to < code > extend()</ code > and can be used instead when it reads better.</ p >
7144< h3 > Pitfalls</ h3 >
7245< p > Do not confuse < code > extend()</ code > with < code > append()</ code > : < code > my_list.append(other_collection)</ code > adds the collection itself as a
7346single element, while < code > extend()</ code > adds its elements.</ p >
74- < p > A loop that appends to the list it iterates over never terminates, while < code > my_list.extend(my_list)</ code > simply doubles the list. The fix is
75- behavior-preserving in every other case, so make sure the loop you replace iterates over a different collection.</ p >
47+ < p > A loop that appends to the list it iterates over never terminates, while < code > my_list.extend(my_list)</ code > simply doubles the list. Make sure
48+ the loop you replace iterates over a different collection.</ p >
49+ < p > After a for-loop, the loop variable stays bound to the last element, while < code > extend()</ code > defines no variable. If code after the loop reads
50+ that variable, keep it defined before replacing the loop.</ p >
7651< h2 > Resources</ h2 >
7752< h3 > Documentation</ h3 >
7853< ul >
@@ -82,6 +57,7 @@ <h3>Documentation</h3>
8257</ ul >
8358< h3 > Related rules</ h3 >
8459< ul >
85- < li > {rule:python:S8502} - "set.update()" should be used instead of consecutive "add()" calls</ li >
60+ < li > {rule:python:S8502} - "set.update()" should be used instead of a for-loop with "add()"</ li >
61+ < li > {rule:python:S9454} - "list.extend()" should be used instead of consecutive "append()" calls</ li >
8662</ ul >
8763
0 commit comments