Skip to content

Latest commit

 

History

History
2463 lines (1633 loc) · 41.3 KB

checks.md

File metadata and controls

2463 lines (1633 loc) · 41.3 KB

Available Checks

FURB100: use-pathlib-with-suffix

Categories: pathlib

A common operation is changing the extension of a file. If you have an existing Path object, you don't need to convert it to a string, slice it, and append a new extension. Instead, use the with_suffix() method:

Bad:

new_filepath = str(Path("file.txt"))[:4] + ".md"

Good:

new_filepath = Path("file.txt").with_suffix(".md")

FURB101: use-pathlib-read-text-read-bytes

Categories: pathlib

When you just want to save the contents of a file to a variable, using a with block is a bit overkill. A simpler alternative is to use pathlib's read_text() function:

Bad:

with open(filename) as f:
    contents = f.read()

Good:

contents = Path(filename).read_text()

FURB102: use-startswith-endswith-tuple

Categories: string

startswith() and endswith() both take a tuple, so instead of calling startswith() multiple times on the same string, you can check them all at once:

Bad:

name = "bob"
if name.startswith("b") or name.startswith("B"):
    pass

Good:

name = "bob"
if name.startswith(("b", "B")):
    pass

FURB103: use-pathlib-write-text-write-bytes

Categories: pathlib

When you just want to save some contents to a file, using a with block is a bit overkill. Instead you can use pathlib's write_text() method:

Bad:

with open(filename, "w") as f:
    f.write("hello world")

Good:

Path(filename).write_text("hello world")

FURB104: use-pathlib-cwd

Categories: pathlib

A modern alternative to os.getcwd() is the Path.cwd() method:

Bad:

cwd = os.getcwd()

Good:

cwd = Path.cwd()

FURB105: simplify-print

Categories: builtin readability

print("") can be simplified to just print().

FURB106: use-expandtabs

Categories: string

If you want to expand the tabs at the start of a string, don't use .replace("\t", " " * 8), use .expandtabs() instead. Note that this only works if the tabs are at the start of the string, since expandtabs() will expand each tab to the nearest tab column.

Bad:

spaces_8 = "\thello world".replace("\t", " " * 8)
spaces_4 = "\thello world".replace("\t", "    ")

Good:

spaces_8 = "\thello world".expandtabs()
spaces_4 = "\thello world".expandtabs(4)

FURB107: use-with-suppress

Categories: contextlib readability

Often times you want to handle an exception and just ignore it. You can do this with a try/except block with a single pass in the except block, but there is a simpler and more concise way using the suppress() function from contextlib:

Bad:

try:
    f()

except FileNotFoundError:
    pass

Good:

with suppress(FileNotFoundError):
    f()

Note: suppress() is slower than using try/except, so for performance critical code you might consider ignoring this check.

FURB108: use-in-oper

Categories: logical readability

When comparing a value to multiple possible options, don't or multiple comparison checks, use a single in expr:

Bad:

if x == "abc" or x == "def":
    pass

Good:

if x in ("abc", "def"):
    pass

Note: This should not be used if the operands depend on boolean short circuiting, since the operands will be eagerly evaluated. This is primarily useful for comparing against a range of constant values.

FURB109: use-consistent-in-bracket

Categories: iterable readability

Since tuple, list, and set literals can be used with the in operator, it is best to pick one and stick with it.

Bad:

for x in (1, 2, 3):
    pass

nums = [str(x) for x in [1, 2, 3]]

Good:

for x in (1, 2, 3):
    pass

nums = [str(x) for x in (1, 2, 3)]

FURB110: use-or-oper

Categories: logical readability

Sometimes the ternary operator (aka, inline if statements) can be simplified to a single or expression.

Bad:

z = x if x else y

Good:

z = x or y

Note: if x depends on side-effects, then this check should be ignored.

FURB111: use-func-name

Categories: performance readability

Don't use a lambda if its only forwarding its arguments to a function.

Bad:

predicate = lambda x: bool(x)

some_func(lambda x, y: print(x, y))

Good:

predicate = bool

some_func(print)

In addition, don't use lambdas when you want a default value for a literal type:

Bad:

counter = defaultdict(lambda: 0)
multimap = defaultdict(lambda: [])

Good:

counter = defaultdict(int)
multimap = defaultdict(list)

FURB112: use-literal

Categories: pythonic readability

Using list and dict without any arguments is slower, and not Pythonic. Use [] and {} instead:

Bad:

nums = list()
books = dict()

Good:

nums = []
books = {}

FURB113: use-list-extend

Categories: list

When appending multiple values to a list, you can use the .extend() method to add an iterable to the end of an existing list. This way, you don't have to call .append() on every element:

Bad:

nums = [1, 2, 3]

nums.append(4)
nums.append(5)
nums.append(6)

Good:

nums = [1, 2, 3]

nums.extend((4, 5, 6))

FURB114: no-double-not

Categories: builtin readability truthy

Double negatives are confusing, so use bool(x) instead of not not x.

Bad:

if not not value:
    pass

Good:

if value:
    pass

FURB115: no-len-compare

Categories: iterable truthy

Don't check a container's length to determine if it is empty or not, use a truthiness check instead:

Bad:

name = "bob"
if len(name) == 0:
    pass

nums = [1, 2, 3]
if len(nums) >= 1:
    pass

Good:

name = "bob"
if not name:
    pass

nums = [1, 2, 3]
if nums:
    pass

FURB116: use-fstring-number-format

Categories: builtin fstring

The bin(), oct(), and hex() functions return the string representation of a number but with a prefix attached. If you don't want the prefix, you might be tempted to just slice it off, but using an f-string will give you more flexibility and let you work with negative numbers:

Bad:

print(bin(1337)[2:])

Good:

print(f"{1337:b}")

FURB117: use-pathlib-open

Categories: pathlib

When you want to open a Path object, don't pass it to open(), just call .open() on the Path object itself:

Bad:

path = Path("filename")

with open(path) as f:
    pass

Good:

path = Path("filename")

with path.open() as f:
    pass

FURB118: use-operator

Categories: operator

Don't write lambdas/functions to wrap builtin operators, use the operator module instead:

Bad:

from functools import reduce

nums = [1, 2, 3]

print(reduce(lambda x, y: x + y, nums))  # 6

Good:

from functools import reduce
from operator import add

nums = [1, 2, 3]

print(reduce(add, nums))  # 6

In addition, the operator.itemgetter() function can be used to get one or more items from an object, removing the need to create a lambda just to extract values from an object:

Bad:

row = (1, "Some text", True)

transform = lambda x: (x[2], x[0])

Good:

from operator import itemgetter

row = (1, "Some text", True)

transform = itemgetter(2, 0)

FURB119: use-fstring-format

Categories: builtin fstring

Certain expressions which are passed to f-strings are redundant because the f-string itself is capable of formatting it. For example:

Bad:

print(f"{bin(1337)}")

print(f"{ascii(input())}")

print(f"{str(123)}")

Good:

print(f"{1337:#b}")

print(f"{input()!a}")

print(f"{123}")

FURB120: use-implicit-default

Categories:

Don't pass an argument if it is the same as the default value:

Bad:

def greet(name: str = "bob") -> None:
    print(f"Hello {name}")

greet("bob")

{}.get("some key", None)

Good:

def greet(name: str = "bob") -> None:
    print(f"Hello {name}")

greet()

{}.get("some key")

FURB121: use-isinstance-issubclass-tuple

Categories: python310 readability

isinstance() and issubclass() both take tuple arguments, so instead of calling them multiple times for the same object, you can check all of them at once:

Bad:

if isinstance(num, float) or isinstance(num, int):
    pass

Good:

if isinstance(num, (float, int)):
    pass

Note: In Python 3.10+, you can also pass type unions as the second param to these functions:

if isinstance(num, float | int):
    pass

FURB122: use-writelines

Categories: builtin readability

When you want to write a list of lines to a file, don't call .write() for every line, use .writelines() instead:

Bad:

lines = ["line 1\n", "line 2\n", "line 3\n"]

with open("file") as f:
    for line in lines:
        f.write(line)

Good:

lines = ["line 1\n", "line 2\n", "line 3\n"]

with open("file") as f:
    f.writelines(lines)

FURB123: no-redundant-cast

Categories: readability

Don't cast a variable or literal if it is already of that type. This usually is the result of not realizing a type is already the type you want, or artifacts of some debugging code. One example of where this might be intentional is when using container types like dict or list, which will create a shallow copy. If that is the case, it might be preferable to use .copy() instead, since it makes it more explicit that a copy is taking place.

Examples:

Bad:

name = str("bob")
num = int(123)

ages = {"bob": 123}
copy = dict(ages)

Good:

name = "bob"
num = 123

ages = {"bob": 123}
copy = ages.copy()

FURB124: use-comparison-chain

Categories: logical readability

When checking that multiple objects are equal to each other, don't use an and expression. Use a comparison chain instead, for example:

Bad:

if x == y and x == z:
    pass

# and

if x is None and y is None:
    pass

Good:

if x == y == z:
    pass

# and

if x is y is None:
    pass

Note: if x depends on side-effects, then this check should be ignored.

FURB125: no-redundant-return

Categories: control-flow readability

Don't explicitly return if you are already at the end of the control flow for the current function:

Bad:

def func():
    print("hello world!")

    return

def func2(x):
    if x == 1:
        print("x is 1")

    else:
        print("x is not 1")

        return

Good:

def func():
    print("hello world!")

def func2(x):
    if x == 1:
        print("x is 1")

    else:
        print("x is not 1")

FURB126: simplify-return

Categories: control-flow readability

Sometimes a return statement can be written more succinctly:

Bad:

def index_or_default(nums: list[Any], index: int, default: Any):
    if index >= len(nums):
        return default

    else:
        return nums[index]

def is_on_axis(position: tuple[int, int]) -> bool:
    match position:
        case (0, _) | (_, 0):
            return True

        case _:
            return False

Good:

def index_or_default(nums: list[Any], index: int, default: Any):
    if index >= len(nums):
        return default

    return nums[index]

def is_on_axis(position: tuple[int, int]) -> bool:
    match position:
        case (0, _) | (_, 0):
            return True

    return False

FURB127: no-with-assign

Categories: readability scoping

Due to Python's scoping rules, you can use a variable that has gone "out of scope" so long as all previous code paths can bind to it. Long story short, you don't need to declare a variable before you assign it in a with statement:

Bad:

x = ""

with open("file.txt") as f:
    x = f.read()

Good:

with open("file.txt") as f:
    x = f.read()

FURB128: use-tuple-unpack-swap

Categories: readability

You don't need to use a temporary variable to swap 2 variables, you can use tuple unpacking instead:

Bad:

temp = x
x = y
y = temp

Good:

x, y = y, x

FURB129: simplify-readlines

Categories: builtin readability

When iterating over a file object line-by-line you don't need to add .readlines(), simply iterate over the object itself. This assumes you aren't passing an argument to readlines().

Bad:

with open("file.txt") as f:
    for line in f.readlines():
        ...

Good:

with open("file.txt") as f:
    for line in f:
        ...

FURB130: no-in-dict-keys

Categories: dict readability

If you only want to check if a key exists in a dictionary, you don't need to call .keys() first, just use in on the dictionary itself:

Bad:

d = {"key": "value"}

if "key" in d.keys():
    ...

Good:

d = {"key": "value"}

if "key" in d:
    ...

FURB131: use-clear

Categories: builtin readability

Slice expressions can be used to replace part a list without reassigning it. If you want to clear all the elements out of a list while maintaining the same reference, don't use del x[:] or x[:] = [], use the faster x.clear() method instead.

Bad:

nums = [1, 2, 3]

del nums[:]
# or
nums[:] = []

Good:

nums = [1, 2, 3]

nums.clear()

FURB132: use-set-discard

Categories: readability set

If you want to remove a value from a set regardless of whether it exists or not, use the discard() method instead of remove():

Bad:

nums = {123, 456}

if 123 in nums:
    nums.remove(123)

Good:

nums = {123, 456}

nums.discard(123)

FURB133: no-redundant-continue

Categories: control-flow readability

Don't explicitly continue if you are already at the end of the control flow for the current for/while loop:

Bad:

def func():
    for _ in range(10):
        print("hello world!")

        continue

def func2(x):
    for x in range(10):
        if x == 1:
            print("x is 1")

        else:
            print("x is not 1")

            continue

Good:

def func():
    for _ in range(10):
        print("hello world!")

def func2(x):
    for x in range(10):
        if x == 1:
            print("x is 1")

        else:
            print("x is not 1")

FURB134: use-cache

Categories: functools python39 readability

Python 3.9 introduces the @cache decorator which can be used as a short-hand for @lru_cache(maxsize=None).

Bad:

from functools import lru_cache

@lru_cache(maxsize=None)
def f(x: int) -> int:
    return x + 1

Good:

from functools import cache

@cache
def f(x: int) -> int:
    return x + 1

FURB135: no-ignored-dict-items

Categories: dict

Don't use .items() on a dict if you only care about the keys or the values, but not both:

Bad:

books = {"Frank Herbert": "Dune"}

for author, _ in books.items():
    print(author)

for _, book in books.items():
    print(book)

Good:

books = {"Frank Herbert": "Dune"}

for author in books:
    print(author)

for book in books.values():
    print(book)

FURB136: use-min-max

Categories: builtin logical readability

Certain ternary expressions can be written more succinctly using the builtin min/max functions:

Bad:

score1 = 90
score2 = 99

highest_score = score1 if score1 > score2 else score2

Good:

score1 = 90
score2 = 99

highest_score = max(score1, score2)

FURB137: simplify-comprehension

Categories: builtin iterable readability

Often times generator expressions and list/set/dict comprehensions can be written more succinctly. For example, passing a list comprehension to a function when a generator expression would suffice, or using the shorthand notation in the case of list and set. For example:

Bad:

nums = [1, 1, 2, 3]

nums_times_10 = list(num * 10 for num in nums)
unique_squares = set(num ** 2 for num in nums)
number_tuple = tuple([num ** 2 for num in nums])

Good:

nums = [1, 1, 2, 3]

nums_times_10 = [num * 10 for num in nums]
unique_squares = {num ** 2 for num in nums}
number_tuple = tuple(num ** 2 for num in nums)

FURB138: use-list-comprehension

Categories: performance readability

When constructing a new list it is usually more performant to use a list comprehension, and in some cases, it can be more readable.

Bad:

nums = [1, 2, 3, 4]
odds = []

for num in nums:
    if num % 2:
        odds.append(num)

Good:

nums = [1, 2, 3, 4]
odds = [num for num in nums if num % 2]

FURB139: no-multiline-strip

Categories: readability

If you want to define a multi-line string but don't want a leading/trailing newline, use a continuation character ('') instead of calling lstrip(), rstrip(), or strip().

Bad:

"""
This is some docstring
""".lstrip()

"""
This is another docstring
""".strip()

Good:

"""\
This is some docstring
"""

"""\
This is another docstring\
"""

FURB140: use-starmap

Categories: itertools performance

If you only want to iterate and unpack values so that you can pass them to a function (in the same order and with no modifications), you should use the more performant starmap function:

Bad:

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)
)

Good:

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)))

FURB141: use-pathlib-exists

Categories: pathlib

When checking whether a file exists or not, try and use the more modern pathlib module instead of os.path.

Bad:

import os

if os.path.exists("filename"):
    pass

Good:

from pathlib import Path

if Path("filename").exists():
    pass

FURB142: no-set-for-loop

Categories: builtin

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.

Bad:

sentence = "hello world"
vowels = "aeiou"
letters = set(sentence)

for vowel in vowels:
    letters.discard(vowel)

Good:

sentence = "hello world"
vowels = "aeiou"
letters = set(sentence)

letters.difference_update(vowels)

FURB143: no-default-or

Categories: logical readability

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.

Bad:

def is_markdown_header(line: str) -> bool:
    return (line or "").startswith("#")

Good:

def is_markdown_header(line: str) -> bool:
    return line.startswith("#")

FURB144: use-pathlib-unlink

Categories: pathlib

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.

Bad:

import os

os.remove("filename")

Good:

from pathlib import Path

Path("filename").unlink()

FURB145: no-slice-copy

Categories: readability

Don't use a slice expression (with no bounds) to make a copy of something, use the more readable .copy() method instead:

Bad:

nums = [3.1415, 1234]
copy = nums[:]

Good:

nums = [3.1415, 1234]
copy = nums.copy()

FURB146: use-pathlib-is-funcs

Categories: pathlib

Don't use the os.path.isfile (or similar) functions, use the more modern pathlib module instead:

Bad:

if os.path.isfile("file.txt"):
    pass

Good:

if Path("file.txt").is_file():
    pass

FURB147: no-path-join

Categories: pathlib

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!")

Note that this check is disabled by default because Path() returns a Path object, not a string, meaning that the Path object will propagate through your code. This might be what you want, and might encourage you to use the pathlib module in more places, but since it is not a drop-in replacement it is disabled by default.

FURB148: no-ignored-enumerate-items

Categories: builtin

Don't use enumerate if you are disregarding either the index or the value:

Bad:

books = ["Ender's Game", "The Black Swan"]

for index, _ in enumerate(books):
    print(index)

for _, book in enumerate(books):
    print(book)

Good:

books = ["Ender's Game", "The Black Swan"]

for index in range(len(books)):
    print(index)

for book in books:
    print(book)

FURB149: no-bool-literal-compare

Categories: logical readability truthy

Don't use is or == to check if a boolean is True or False, simply use the name itself:

Bad:

failed = True

if failed is True:
    print("You failed")

Good:

failed = True

if failed:
    print("You failed")

FURB150: use-pathlib-mkdir

Categories: pathlib

Use the mkdir method from the pathlib library instead of using the mkdir and makedirs functions from the os library: the pathlib library is more modern and provides better flexibility over the construction and manipulation of file paths.

Bad:

import os

os.mkdir("new_folder")

Good:

from pathlib import Path

Path("new_folder").mkdir()

FURB151: use-pathlib-touch

Categories: pathlib

Don't use open(x, "w").close() if you just want to create an empty file, use the less confusing Path.touch() method instead.

Bad:

open("file.txt", "w").close()

Good:

from pathlib import Path

Path("file.txt").touch()

This check is disabled by default because touch() will throw a FileExistsError if the file already exists, and (at least on Linux) it sets different file permissions, meaning it is not a drop-in replacement. If you don't care about the file permissions or know that the file doesn't exist beforehand this check may be for you.

FURB152: use-math-constant

Categories: math readability

Don't hardcode math constants like pi, tau, or e, use the math.pi, math.tau, or math.e constants respectively.

Bad:

def area(r: float) -> float:
    return 3.1415 * r * r

Good:

import math

def area(r: float) -> float:
    return math.pi * r * r

FURB153: simplify-path-constructor

Categories: pathlib readability

The Path() constructor defaults to the current directory, so don't pass the current directory explicitly.

Bad:

file = Path(".")

Good:

file = Path()

Note: Lots of different values can trigger this check, including ".", "", os.curdir, and os.path.curdir.

FURB154: simplify-global-and-nonlocal

Categories: builtin readability

The global and nonlocal keywords can take multiple comma-separated names, removing the need for multiple lines.

Bad:

def some_func():
    global x
    global y

    print(x, y)

Good:

def some_func():
    global x, y

    print(x, y)

FURB155: use-pathlib-stat

Categories: pathlib

Don't use the os.path.getsize (or similar) functions, use the more modern pathlib module instead:

Bad:

if os.path.getsize("file.txt"):
    pass

Good:

if Path("file.txt").stat().st_size:
    pass

FURB156: use-string-charsets

Categories: readability string

Python includes some pre-defined charsets such as digits (0-9), upper and lower case alpha characters, and so on. You don't have to define them yourself, and they are usually more readable.

Bad:

digits = "0123456789"

if c in digits:
    pass

if c in "0123456789abcdefABCDEF":
    pass

Good:

if c in string.digits:
    pass

if c in string.hexdigits:
    pass

Note that when using a literal string, the corresponding string.xyz value must be exact, but when used in an in comparison, the characters can be out of order since in will compare every character in the string.

FURB157: simplify-decimal-ctor

Categories: decimal

Under certain circumstances the Decimal() constructor can be made more succinct.

Bad:

if x == Decimal("0"):
    pass

if y == Decimal(float("Infinity")):
    pass

Good:

if x == Decimal(0):
    pass

if y == Decimal("Infinity"):
    pass

FURB158: simplify-as-pattern-with-builtin

Categories: pattern-matching readability

When pattern matching builtin classes such as int() and str(), don't use an as pattern to bind to the value, since the most common builtin classes can use positional patterns instead.

Bad:

match x:
    case str() as name:
        print(f"Hello {name}")

Good:

match x:
    case str(name):
        print(f"Hello {name}")

FURB159: simplify-strip

Categories: readability string

In some situations the .lstrip(), .rstrip() and .strip() string methods can be written more succinctly: strip() is the same thing as calling both lstrip() and rstrip() together, and all the strip functions take an iterable argument of the characters to strip, meaning you don't need to call strip methods multiple times with different arguments, you can just concatenate them and call it once.

Bad:

name = input().lstrip().rstrip()

num = "  -123".lstrip(" ").lstrip("-")

Good:

name = input().strip()

num = "  -123".lstrip(" -")

FURB160: no-redundant-assignment

Categories: readability

Sometimes when you are debugging (or copy-pasting code) you will end up with a variable that is assigning itself to itself. These lines can be removed.

Bad:

name = input("What is your name? ")
name = name

Good:

name = input("What is your name? ")

FURB161: use-bit-count

Categories: builtin performance python310 readability

Python 3.10 adds a very helpful bit_count() function for integers which counts the number of set bits. This new function is more descriptive and faster compared to converting/counting characters in a string.

Bad:

x = bin(0b1010).count("1")

assert x == 2

Good:

x = 0b1010.bit_count()

assert x == 2

FURB162: simplify-fromisoformat

Categories: datetime python311 readability

Python 3.11 adds support for parsing UTC timestamps that end with Z, thus removing the need to strip and append the +00:00 timezone.

Bad:

date = "2023-02-21T02:23:15Z"

start_date = datetime.fromisoformat(date.replace("Z", "+00:00"))

Good:

date = "2023-02-21T02:23:15Z"

start_date = datetime.fromisoformat(date)

FURB163: simplify-math-log

Categories: math readability

Use the shorthand log2 and log10 functions instead of passing 2 or 10 as the second argument to the log function. If math.e is used as the second argument, just use math.log(x) instead, since e is the default.

Bad:

power = math.log(x, 10)

Good:

power = math.log10(x)

FURB164: no-from-float

Categories: decimal fractions readability

When constructing a Fraction or Decimal using a float, don't use the from_float() or from_decimal() class methods: Just use the more concise Fraction() and Decimal() class constructors instead.

Bad:

ratio = Fraction.from_float(1.2)
score = Decimal.from_float(98.0)

Good:

ratio = Fraction(1.2)
score = Decimal(98.0)

FURB165: no-temp-class-object

Categories: readability

You don't need to construct a class object to call a static method or a class method, just invoke the method on the class directly:

Bad:

cwd = Path().cwd()

Good:

cwd = Path.cwd()

FURB166: use-int-base-zero

Categories: builtin readability

When converting a string starting with 0b, 0o, or 0x to an int, you don't need to slice the string and set the base yourself: just call int() with a base of zero. Doing this will autodeduce the correct base to use based on the string prefix.

Bad:

num = "0xABC"

if num.startswith("0b"):
    i = int(num[2:], 2)
elif num.startswith("0o"):
    i = int(num[2:], 8)
elif num.startswith("0x"):
    i = int(num[2:], 16)

print(i)

Good:

num = "0xABC"

i = int(num, 0)

print(i)

This check is disabled by default because there is no way for Refurb to detect whether the prefixes that are being stripped are valid Python int prefixes (like 0x) or some other prefix which would fail if parsed using this method.

FURB167: use-long-regex-flag

Categories: readability regex

Regex operations can be changed using flags such as re.I, which will make the regex case-insensitive. These single-character flag names can be harder to read/remember, and should be replaced with the longer aliases so that they are more descriptive.

Bad:

if re.match("^hello", "hello world", re.I):
    pass

Good:

if re.match("^hello", "hello world", re.IGNORECASE):
    pass

FURB168: no-isinstance-type-none

Categories: pythonic readability

Checking if an object is None using isinstance() is un-pythonic: use an is comparison instead.

Bad:

x = 123

if isinstance(x, type(None)):
    pass

Good:

x = 123

if x is None:
    pass

FURB169: no-is-type-none

Categories: pythonic readability

Don't use type(None) to check if the type of an object is None, use an is comparison instead.

Bad:

x = 123

if type(x) is type(None):
    pass

Good:

x = 123

if x is None:
    pass

FURB170: use-regex-pattern-methods

Categories: performance readability regex

If you are passing a compiled regular expression to a regex function, consider calling the regex method on the pattern itself: It is faster, and can improve readability.

Bad:

import re

COMMENT = re.compile(".*(#.*)")

found_comment = re.match(COMMENT, "this is a # comment")

Good:

import re

COMMENT = re.compile(".*(#.*)")

found_comment = COMMENT.match("this is a # comment")

FURB171: no-single-item-in

Categories: iterable readability

Don't use in to check against a single value, use == instead:

Bad:

if name in ("bob",):
    pass

Good:

if name == "bob":
    pass

FURB172: use-suffix

Categories: pathlib

When checking the file extension for a Path object don't call endswith() on the name field, directly check against suffix instead.

Bad:

from pathlib import Path

def is_markdown_file(file: Path) -> bool:
    return file.name.endswith(".md")

Good:

from pathlib import Path

def is_markdown_file(file: Path) -> bool:
    return file.suffix == ".md"

Note: The suffix field will only contain the last file extension, so don't use suffix if you are checking for an extension like .tar.gz. Refurb won't warn in those cases, but it is good to remember in case you plan to use this in other places.

FURB173: use-dict-union

Categories: dict readability

Dicts can be created/combined in many ways, one of which is the ** operator (inside the dict), and another is the | operator (used outside the dict). While they both have valid uses, the | operator allows for more flexibility, including using |= to update an existing dict.

See PEP 584 for more info.

Bad:

def add_defaults(settings: dict[str, str]) -> dict[str, str]:
    return {"color": "1", **settings}

Good:

def add_defaults(settings: dict[str, str]) -> dict[str, str]:
    return {"color": "1"} | settings

FURB174: simplify-token-function

Categories: readability secrets

Depending on how you are using the secrets module, there might be more expressive ways of writing what it is you're trying to write.

Bad:

random_hex = token_bytes().hex()
random_url = token_urlsafe()[:16]

Good:

random_hex = token_hex()
random_url = token_urlsafe(16)

FURB175: simplify-fastapi-query

Categories: fastapi readability

FastAPI will automatically pass along query parameters to your function, so you only need to use Query() when you use params other than default.

Bad:

@app.get("/")
def index(name: str = Query()) -> str:
    return f"Your name is {name}"

Good:

@app.get("/")
def index(name: str) -> str:
    return f"Your name is {name}"

FURB176: unreliable-utc-usage

Categories: datetime

Because naive datetime objects are treated by many datetime methods as local times, it is preferred to use aware datetimes to represent times in UTC.

This check affects datetime.utcnow and datetime.utcfromtimestamp.

Bad:

from datetime import datetime

now = datetime.utcnow()
past_date = datetime.utcfromtimestamp(some_timestamp)

Good:

from datetime import datetime, timezone

datetime.now(timezone.utc)
datetime.fromtimestamp(some_timestamp, tz=timezone.utc)

FURB177: no-implicit-cwd

Categories: pathlib

If you want to get the current working directory don't call resolve() on an empty Path() object, use Path.cwd() instead.

Bad:

cwd = Path().resolve()

Good:

cwd = Path.cwd()

FURB178: use-shlex-join

Categories: readability shlex

When using shlex to escape and join a bunch of strings consider using the shlex.join method instead.

Bad:

args = ["hello", "world!"]

cmd = " ".join(shlex.quote(arg) for arg in args)

Good:

args = ["hello", "world!"]

cmd = shlex.join(args)

FURB179: use-chain-from-iterable

Categories: itertools performance readability

When flattening a list of lists, use the chain.from_iterable() function from the itertools stdlib package. This function is faster than native list/generator comprehensions or using sum() with a list default.

Bad:

from itertools import chain

rows = [[1, 2], [3, 4]]

# using list comprehension
flat = [col for row in rows for col in row]

# using sum()
flat = sum(rows, [])

# using chain(*x)
flat = chain(*rows)

Good:

from itertools import chain

rows = [[1, 2], [3, 4]]

flat = chain.from_iterable(rows)

Note: chain.from_iterable() returns an iterator, which means you might need to wrap it in list() depending on your use case. Refurb cannot detect this (yet), so this is something you will need to keep in mind.

Note: chain(*x) may be marginally faster/slower depending on the length of x. Since * might potentially expand to a lot of arguments, it is better to use chain.from_iterable() when you are unsure.

FURB180: use-abc-shorthand

Categories: abc readability

Instead of setting metaclass directly, inherit from the ABC wrapper class. This is semantically the same thing, but more succinct.

Bad:

class C(metaclass=ABCMeta):
    pass

Good:

class C(ABC):
    pass

FURB181: use-hexdigest-hashlib

Categories: hashlib readability

Use .hexdigest() to get a hex digest from a hash.

Bad:

from hashlib import sha512

hashed = sha512(b"some data").digest().hex()

Good:

from hashlib import sha512

hashed = sha512(b"some data").hexdigest()

FURB182: simplify-hashlib-ctor

Categories: hashlib readability

You can pass data into hashlib constructors, so instead of creating a hash object and immediately updating it, pass the data directly.

Bad:

from hashlib import sha512

h = sha512()
h.update(b"data")

Good:

from hashlib import sha512

h = sha512(b"data")

FURB183: use-str-func

Categories: readability

If you want to stringify a single value without concatenating anything, use the str() function instead.

Bad:

nums = [123, 456]
num = f"{num[0]}"

Good:

nums = [123, 456]
num = str(num[0])

FURB184: use-fluid-interface

Categories: readability

When an API has a Fluent Interface (the ability to chain multiple calls together), you should chain those calls instead of repeatedly assigning and using the value. Sometimes a return statement can be written more succinctly:

Bad:

def get_tensors(device: str) -> torch.Tensor:
    t1 = torch.ones(2, 1)
    t2 = t1.long()
    t3 = t2.to(device)
    return t3

def process(file_name: str):
    common_columns = ["col1_renamed", "col2_renamed", "custom_col"]
    df = spark.read.parquet(file_name)
    df = df \
        .withColumnRenamed('col1', 'col1_renamed') \
        .withColumnRenamed('col2', 'col2_renamed')
    df = df \
        .select(common_columns) \
        .withColumn('service_type', F.lit('green'))
    return df

Good:

def get_tensors(device: str) -> torch.Tensor:
    t3 = (
        torch.ones(2, 1)
        .long()
        .to(device)
    )
    return t3

def process(file_name: str):
    common_columns = ["col1_renamed", "col2_renamed", "custom_col"]
    df = (
        spark.read.parquet(file_name)
        .withColumnRenamed('col1', 'col1_renamed')
        .withColumnRenamed('col2', 'col2_renamed')
        .select(common_columns)
        .withColumn('service_type', F.lit('green'))
    )
    return df

FURB185: no-copy-with-merge

Categories: readability

You don't need to call .copy() on a dict/set when using it in a union since the original dict/set is not modified.

Bad:

d = {"a": 1}

merged = d.copy() | {"b": 2}

Good:

d = {"a": 1}

merged = d | {"b": 2}

FURB186: use-sort

Categories: performance readability

Don't use sorted() to sort a list and reassign it to itself, use the faster in-place .sort() method instead.

Bad:

names = ["Bob", "Alice", "Charlie"]

names = sorted(names)

Good:

names = ["Bob", "Alice", "Charlie"]

names.sort()

FURB187: use-reverse

Categories: performance readability

Don't use x[::-1] or reversed(x) to reverse a list and reassign it to itself, use the faster in-place .reverse() method instead.

Bad:

names = ["Bob", "Alice", "Charlie"]

names = reversed(names)
# or
names = list(reversed(names))
# or
names = names[::-1]

Good:

names = ["Bob", "Alice", "Charlie"]

names.reverse()

FURB188: remove-prefix-or-suffix

Categories: performance readability string

Don't explicitly check a string prefix/suffix if you're only going to remove it, use .removeprefix() or .removesuffix() instead.

Bad:

def strip_txt_extension(filename: str) -> str:
    return filename[:-4] if filename.endswith(".txt") else filename

Good:

def strip_txt_extension(filename: str) -> str:
    return filename.removesuffix(".txt")

FURB189: no-subclass-builtin

Categories: collections

Subclassing dict, list, or str objects can be error prone, use the UserDict, UserList, and UserStr objects from the collections module instead.

Bad:

class CaseInsensitiveDict(dict):
    ...

Good:

from collections import UserDict

class CaseInsensitiveDict(UserDict):
    ...

Note: isinstance() checks for dict, list, and str types will fail when using the corresponding User class. If you need to pass custom dict or list objects to code you don't control, ignore this check. If you do control the code, consider using the following type checks instead:

  • dict -> collections.abc.MutableMapping
  • list -> collections.abc.MutableSequence
  • str -> No such conversion exists

FURB190: use-str-method

Categories: performance readability

Don't use a lambda function to call a no-arg method on a string, use the name of the string method directly. It is faster, and often times improves readability.

Bad:

def normalize_phone_number(phone_number: str) -> int:
    digits = filter(lambda x: x.isdigit(), phone_number)

    return int("".join(digits))

Good:

def normalize_phone_number(phone_number: str) -> int:
    digits = filter(str.isdigit, phone_number)

    return int("".join(digits))

FURB191: use-isinstance-bool

Categories: readability

Don't check if a value is True or False using in, use an isinstance() call.

Bad:

if value in {True, False}:
    pass

Good:

if isinstance(value, bool):
    pass

FURB192: no-sorted-min-max

Categories: builtin performance readability

Don't use sorted() to get the min/max value out of an iterable element, use min() or max().

Bad:

nums = [3, 1, 4, 1, 5]

lowest = sorted(nums)[0]
highest = sorted(nums)[-1]

Good:

nums = [3, 1, 4, 1, 5]

lowest = min(nums)
highest = max(nums)