This release includes 10 new checks, general bug fixes, and some quality-of-life improvements as well!
Add "use starmap" check (FURB140)
Often times you want to iterate over some zip()
'd values, unpack them, and pass each of the values to function. The starmap
function gives you a faster and more succinct way to do just that.
The following code:
scores = [85, 100, 60]
passing_scores = [60, 80, 70]
def passed_test(score: int, passing_score: int) -> bool:
return score >= passing_score
passed_all_tests = all(
passed_test(score, passing_score)
for score, passing_score
in zip(scores, passing_scores)
)
Can be re-written as follows:
from itertools import starmap
scores = [85, 100, 60]
passing_scores = [60, 80, 70]
def passed_test(score: int, passing_score: int) -> bool:
return score >= passing_score
passed_all_tests = all(starmap(passed_test, zip(scores, passing_scores)))
Add "pathlib exists" check (FURB141)
When checking whether a file exists or not, try and use the more modern pathlib
module instead of os.path
.
This:
import os
if os.path.exists("filename"):
pass
Can be written like this:
from pathlib import Path
if Path("filename").exists():
pass
Add "no set op in for loop" check (FURB142)
When you want to add/remove a bunch of items to/from a set, don't use a for loop, call the appropriate method on the set itself.
For example, this code:
sentence = "hello world"
vowels = "aeiou"
letters = set(sentence)
for vowel in vowels:
letters.discard(vowel)
Can be rewritten like so:
sentence = "hello world"
vowels = "aeiou"
letters = set(sentence)
letters.difference_update(vowels)
# or
letters -= set(vowels)
Add "no or default" check (FURB143)
Don't check an expression to see if it is falsey then assign the same falsey value to it. For example, if an expression used to be of type int | None
, checking if the expression is falsey would make sense, since it could be None
or 0
. But, if the expression is changed to be of type int
, the falsey value is just 0
, so setting it to 0
if it is falsey (0
) is redundant.
This:
def is_markdown_header(line: str) -> bool:
return (line or "").startswith("#")
Can be written like so:
def is_markdown_header(line: str) -> bool:
return line.startswith("#")
Add "pathlib unlink" check (FURB144)
When removing a file, use the more modern Path.unlink()
method instead of os.remove()
or os.unlink()
: The pathlib
module allows for more flexibility when it comes to traversing folders, building file paths, and accessing/modifying files.
This:
import os
os.remove("filename")
Can be written as:
from pathlib import Path
Path("filename").unlink()
Add "no slice copy" check (FURB145)
Don't use a slice expression (with no bounds) to make a copy of something, use the more readable .copy()
method instead.
For example, this:
nums = [3.1415, 1234]
copy = nums[:]
Can be rewritten as:
nums = [3.1415, 1234]
copy = nums.copy()
Add "pathlib is file" check (FURB146)
Don't use the os.path.isfile
(or similar) functions, use the more modern pathlib
module instead:
if os.path.isfile("file.txt"):
pass
Can be rewritten as:
if Path("file.txt").is_file():
pass
Add "pathlib join" check (FURB147)
When joining strings to make a filepath, use the more modern and flexible Path()
object instead of os.path.join
:
Bad:
with open(os.path.join("folder", "file"), "w") as f:
f.write("hello world!")
Good:
from pathlib import Path
with open(Path("folder", "file"), "w") as f:
f.write("hello world!")
# even better ...
with Path("folder", "file").open("w") as f:
f.write("hello world!")
# even better ...
Path("folder", "file").write_text("hello world!")
Add "no ignored enumerate" check (FURB148)
Don't use enumerate()
if you are disregarding either the index or the value:
books = ["Ender's Game", "The Black Swan"]
for index, _ in enumerate(books):
print(index)
for _, book in enumerate(books):
print(book)
This instead should be written as:
books = ["Ender's Game", "The Black Swan"]
for index in range(len(books)):
print(index)
for book in books:
print(book)
Add "no is bool compare" check (FURB149)
Don't use is
or ==
to check if a boolean is True or False, simply use the name itself:
failed = True
if failed is True:
print("You failed")
Should be written as:
failed = True
if failed:
print("You failed")
Allow for comma-separated ignore
, enable
, and disable
CLI flags
Previously, if you wanted to enable/disable/ignore multiple checks at once, you would have to write:
$ refurb src --disable FURB123 --disable FURB124 --disable FURB125
Now you write it like this instead:
$ refurb src --disable FURB123,FURB124,FURB125
What's Changed
- Add "use starmap" check by @dosisod in #137
- Add ability to separate
ignore
,enable
, anddisable
CLI flags with commas by @dosisod in #138 - Add "pathlib exists" check by @dosisod in #139
- Add "no set in for loop" check by @dosisod in #140
- Fix
.pyi
files taking precedence over.py
files by @dosisod in #141 - Add "no or default" check by @dosisod in #142
- Add "pathlib unlink" check by @dosisod in #143
- Add "no slice copy" check by @dosisod in #145
- Add "is file" pathlib check by @dosisod in #147
- Add
noqa
comments to test files by @dosisod in #148 - Add "pathlib join" check by @dosisod in #149
- Colorize CI Output by @dosisod in #150
- Fix FURB135 false positives by @dosisod in #153
- Add "no ignored enumerate" check by @dosisod in #154
- Fix typo in no_unecessary_cast explanation by @jaoxford in #156
- Fix FURB145 emitting error for non-slice index expressions by @dosisod in #157
- Bump packages by @dosisod in #158
- Add
is
operator to FURB124 (use equal chain) by @dosisod in #159 - Add "no is bool compare" check by @dosisod in #160
- Update FURB149 to include
==
and!=
by @dosisod in #161
New Contributors
Full Changelog: v1.8.0...v1.9.0