Skip to content

Too easy to trigger duplicate-code with multiline destructuring assignments #10319

Closed as duplicate of#7920
@ruro

Description

@ruro

Bug description

Consider the following:

mkdir -p example
touch example/__init__.py

cat >example/common.py <<'EOF'
def this_code_is_already_factored_out(a, b):
    del a, b
    return "a", "b", "c", "d"
EOF

cat >example/bar1.py <<'EOF'
from .common import this_code_is_already_factored_out


class Bar1:
    a_lot_of_attributes = 1
    with_long_names = 2
    that_dont_fit = 3
    on_one_line = 4

    def different(self):
        return 0

    def bee(self):
        (
            self.a_lot_of_attributes,
            self.with_long_names,
            self.that_dont_fit,
            self.on_one_line,
        ) = this_code_is_already_factored_out(
            a=1_000_000_000 + 0.000_000_000_1,
            b={2_000_000_000, 3_000_000_000},
        )
EOF

cat >example/bar2.py <<'EOF'
from .common import this_code_is_already_factored_out


class Bar2:
    a_lot_of_attributes = "this"
    with_long_names = "code"
    that_dont_fit = "is"
    on_one_line = "different"

    def woop(self):
        (
            self.a_lot_of_attributes,
            self.with_long_names,
            self.that_dont_fit,
            self.on_one_line,
        ) = this_code_is_already_factored_out(
            a=["the arguments are also long enough"],
            b="so" + "they" + "dont" + "fit" + "on" + "one" + "line",
        )

    def unrelated(self):
        return "clearly different"
EOF

It's a bit silly to treat such multiline destructuring assignments as "lines" for the purposes of duplicate-code / min-similarity-lines. In particular, I am struggling to come up with a way to refactor this kind of code to "satisfy" pylint.

In this case, all the common logic has already been refactored into a separate function and the only thing left is the assignment of the attributes. This can't be fixed by using inheritance, because the woop and bee methods themselves are different (and the Baz1/Baz2 classes are barely related).

I suppose, I could create a separate function

def run_common_code_and_assign(target, a, b):
    (
        target.a_lot_of_attributes,
        target.with_long_names,
        target.that_dont_fit,
        target.on_one_line,
    ) = this_code_is_already_factored_out(
        a=a,
        b=b,
    )

and then use it like

class Bar1:
    def bee(self):
        run_common_code_and_assign(
            target=self,
            a=1_000_000_000 + 0.000_000_000_1,
            b={2_000_000_000, 3_000_000_000},
        )

class Bar2:
    def woop(self):
        run_common_code_and_assign(
            target=self,
            a=["the arguments are also long enough"],
            b="so" + "they" + "dont" + "fit" + "on" + "one" + "line",
        )

However, this essentially makes target an "out parameter" for run_common_code_and_assign, which is an antipattern in Python (and setting class members outside the class/module is also kind of yucky, especially if a_lot_of_attributes are _protected or __private).

Command used

pylint --rcfile= --disable=missing-docstring example

Pylint output

************* Module example.common
example/common.py:1:0: R0801: Similar lines in 2 files
==example.bar1:[13:19]
==example.bar2:[10:16]
        (
            self.a_lot_of_attributes,
            self.with_long_names,
            self.that_dont_fit,
            self.on_one_line,
        ) = this_code_is_already_factored_out( (duplicate-code)

------------------------------------------------------------------
Your code has been rated at 9.57/10 (previous run: 9.57/10, +0.00)

Expected behavior

--------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)

Pylint version

pylint 3.3.6
astroid 3.3.9
Python 3.12.8 (main, Dec  3 2024, 18:42:41) [GCC 14.2.1 20241116]

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs triage 📥Just created, needs acknowledgment, triage, and proper labelling

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions