Skip to content
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

New rule: Prefer in-place operators #8877

Closed
Avasam opened this issue Nov 28, 2023 · 8 comments
Closed

New rule: Prefer in-place operators #8877

Avasam opened this issue Nov 28, 2023 · 8 comments
Labels
help wanted Contributions especially welcome rule Implementing or modifying a lint rule

Comments

@Avasam
Copy link

Avasam commented Nov 28, 2023

I don't think any linter currently checks for this, although I did open a feature request in those I knew of where this feature might be in scope (MartinThoma/flake8-simplify#188 and dosisod/refurb#310). Whether they accept it, (and who knows when it would be implemented), this is still a check I'd like to see in Ruff as I don't directly use those other linters and autofixing should be possible. If you think this is a viable rule.

In-place operators lead to more concise code that is still readable. I'm not sure if there's any objective drawbacks (like a common pitfall for certain types). And performance-wise my understanding is that it should be faster (due to the in-place nature) or equivalent (thanks to Python duck-typing that will fallback to __add__ if __iadd__ is not implemented).

Any of the following:

some_string = (
  some_string
  + "a veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeery long end of string"
)
index = index - 1
a_list = a_list + ["to concat"]
some_set = some_set | {"to concat"}
to_multiply = to_multiply * 5
to_divide = to_divide / 5
to_cube = to_cube ** 3
timeDiffSeconds = timeDiffSeconds % 60
flags = flags & 0x1
# etc.

Could be re-written as:

some_string += "a veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeery long end of string"
index -= 1
a_list += ["to concat"]
some_set |= {"to concat"}
to_multiply *= 5
to_divide /= 5
to_cube **= 3
timeDiffSeconds %= 60
flags &= 0x1
# etc.
@zanieb
Copy link
Member

zanieb commented Nov 28, 2023

Makes sense to me, I'm surprised this does not exist already.

@zanieb zanieb added the rule Implementing or modifying a lint rule label Nov 28, 2023
@Avasam
Copy link
Author

Avasam commented Nov 28, 2023

I just thought of a special case with string interpolation that idk if should be included:

# from
base_value = f"{base_value} end of string"

# to
base_value += " end of string"

Because it would break if base_value is not a str or a type that supports += with a str.

@zanieb
Copy link
Member

zanieb commented Nov 28, 2023

We could support that conditional on the ability to determine that the type of base_value is str.

@dhruvmanila
Copy link
Member

We could first implement the simple cases of var = var <operator> <expr>. I think this could go into the RUF category. If anyone's interested, I'd be happy to give a look at the implementation.

@dhruvmanila dhruvmanila added the help wanted Contributions especially welcome label Nov 30, 2023
@tdulcet
Copy link

tdulcet commented Dec 1, 2023

Maybe this should be a separate rule, but there might be an opportunity for some more micro optimizations here. For example, if it were faster, these:

a_list += ["to concat"]
a_list += ["to concat 1", "to concat 2"]
some_set |= {"to concat"}
some_set |= {"to concat 1", "to concat 2"}
# ect.

could potenchally be further rewritten as:

a_list.append("to concat")
a_list.extend(("to concat 1", "to concat 2"))
some_set.add("to concat")
some_set.update(("to concat 1", "to concat 2"))
# ect.

@Avasam
Copy link
Author

Avasam commented Dec 1, 2023

Increased complexity, may require type information, and I'm not asking for this for a first version / as part of my original feature request, but worth considering: dosisod/refurb#310 (comment)

Because anyone can make their own custom in-place operators, I think it is best to only support built-in types, though there [could] be an option to extend this to all types [...]

Also, I'd probably add these to your list as well:

a = a or b
c = c and d

Re-write as:

a |= b
c &= d

assuming a, b, c and d are bool types.


Maybe this should be a separate rule, but there might be an opportunity for some more micro optimizations here. For example, if it were faster, these:

Different rule imo, and may require type information. But worth investigating. Even if the performance is negligible or too variant between Python versions, it could become a readability/preference rule.

@lshi18
Copy link
Contributor

lshi18 commented Feb 11, 2024

Hi, I've made an attempt implementing this rule. Please help to have a review and let me know your thoughts. Thanks.

charliermarsh pushed a commit that referenced this issue Apr 12, 2024
…9932)

## Summary

Implement new rule: Prefer augmented assignment (#8877). It checks for
the assignment statement with the form of `<expr> = <expr>
<binary-operator> …` with a unsafe fix to use augmented assignment
instead.

## Test Plan

1. Snapshot test is included in the PR.
2. Manually test with playground.
@charliermarsh
Copy link
Member

Closed by #9932.

Glyphack pushed a commit to Glyphack/ruff that referenced this issue Apr 12, 2024
…stral-sh#9932)

## Summary

Implement new rule: Prefer augmented assignment (astral-sh#8877). It checks for
the assignment statement with the form of `<expr> = <expr>
<binary-operator> …` with a unsafe fix to use augmented assignment
instead.

## Test Plan

1. Snapshot test is included in the PR.
2. Manually test with playground.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Contributions especially welcome rule Implementing or modifying a lint rule
Projects
None yet
Development

No branches or pull requests

6 participants