Skip to content

Version 1.9.0

Compare
Choose a tag to compare
@dosisod dosisod released this 19 Dec 04:21
· 218 commits to master since this release
2e90c3c

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

New Contributors

Full Changelog: v1.8.0...v1.9.0