This rule raises an issue when several elements are added to the same set through consecutive add() calls, either as a sequence of statements or through a for-loop.

Why is this an issue?

Adding elements to a set one at a time is unnecessary when the update() method exists. This happens with a sequence of add() statements:

my_set.add(first)
my_set.add(second)
my_set.add(third)

It also happens when a for-loop adds every element of another collection, which is the same sequence of calls spread over an iteration:

for element in other_collection:
    my_set.add(element)

The update() method combines all additions into a single call and avoids the overhead of repeated add() invocations:

my_set.update({first, second, third})
my_set.update(other_collection)

What is the potential impact?

Grouping the additions makes the intent explicit: the reader sees one operation that grows the set instead of a series of independent statements. It 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 when the source collection is large, the difference becomes measurable.

Exceptions

No issue is raised when other statements are interleaved between the add() calls, or when the body of the loop does more than add the loop variable, for example when it filters or transforms the elements:

for element in other_collection:
    if element.is_valid():
        my_set.add(element)

How to fix it

Group the added elements into a single update() call. For consecutive statements, pass the elements as a set literal. For a loop, pass the iterated collection directly, as update() accepts any iterable.

Code examples

Noncompliant code example

def collect_tags(base_tags, name):
    tags = set(base_tags)
    tags.add("archived")  # Noncompliant
    tags.add("verified")
    tags.add(name)
    return tags

Compliant solution

def collect_tags(base_tags, name):
    tags = set(base_tags)
    tags.update({"archived", "verified", name})
    return tags

Noncompliant code example

def collect_results(results, new_results):
    for result in new_results:
        results.add(result)  # Noncompliant
    return results

Compliant solution

def collect_results(results, new_results):
    results.update(new_results)
    return results

How does this work?

update() iterates over its argument and adds every element to the set, so the result is exactly the same as the individual calls. The augmented assignment my_set |= {first, second, third} is equivalent to update() with another set and can be used instead when it is more readable.

Pitfalls

Do not confuse update() with add(): my_set.add(other_collection) adds the collection itself as a single element, and fails when that collection is not hashable, while update() adds its elements.

A loop that adds to the set it iterates over only re-adds elements that are already present, so it leaves the set unchanged and there is nothing to group. Apart from that case, the fix preserves behavior.

Resources

Documentation

Related rules