Releases: dosisod/refurb
Release v2.0.0
This new release greatly improves the type checking capabilities of Refurb. You might find that you are getting a lot more errors in your projects, as well as a few false positives. Please report any incorrect changes you might find!
Add better type deduction
Refurb now makes better use of Mypy's type deduction system, allowing Refurb to find more errors and with better accuracy.
For example, the following is not detected in older versions of Refurb:
nums = [[]]
if len(nums[0]):
print("nums[0] is not empty")
With this new version of Refurb, the type of nums[0]
is inferred as a list
:
$ refurb file.py
file.py:3:4 [FURB115]: Replace `len(nums[0])` with `nums[0]`
While this new type deduction is a great improvement on what existed before, there is still a lot of room for improvement. If you find any typing related bugs, please report it!
Add use-isinstance-bool
check (FURB191)
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
Add use-str-method
check (FURB190)
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))
Add no-subclass-builtin
check (FURB189)
Note: this check is disabled by default, see below.
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
Rename no-del
to use-clear
(FURB131)
Read dbe3126 for more info. In short, FURB131 will now detect both of the following snippets:
nums = [[1, 2, 3], [4, 5, 6]]
nums[0][:] = []
del nums[0][:]
Running Refurb:
$ refurb file.py
file.py:3:1 [FURB131]: Replace `nums[0][:] = []` with `nums[0].clear()`
file.py:4:1 [FURB131]: Replace `del nums[0][:]` with `nums[0].clear()`
What's Changed
- Add
no-subclass-builtin
check by @dosisod in #324 - Add better detection and error messages for len compatible types in FURB115 by @dosisod in #331
Full Changelog: v1.28.0...v2.0.0
Release v1.28.0
This release adds improved error messages, more suggestions for existing checks, and 2 brand new checks!
Add remove-prefix-or-suffix
check (FURB188)
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")
Add use-reverse
check (FURB187)
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()
Detect itemgetter()
in FURB118 (use-operator
)
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)
Detect no-arg lambdas returning default literals in FURB111 (use-func-name
)
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)
Full Changelog: v1.27.0...v1.28.0
Release v1.27.0
This release adds 2 new checks and adds improved detection and error messages for a few other checks!
Add no-copy-with-merge
Check (FURB185)
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}
Add use-sort
Check (FURB186)
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()
Color is now disabled when piping output
Previously Refurb would always emit color unless you turned it off. Now it will check if the output is a TTY, and if so, disable the color.
Improve use-chain-from-iterable
(FURB179)
FURB179 now detects the following snippets:
functools.reduce(operator.add, rows)
functools.reduce(operator.add, rows, [])
functools.reduce(operator.concat, rows)
functools.reduce(operator.concat, rows, [])
These snippets are slow because they use the add
/concat
operator. This will create a new list for each iteration instead of doing it in-place.
Improve use-dict-union
(FURB173)
FURB173 now detects the following snippets:
_ = dict(**x)
_ = dict(x, **y)
_ = dict(**x, **y)
_ = dict(x, a=1)
_ = dict(**x, a=1, b=2)
_ = dict(**x, **y, a=1, b=2)
These can be re-written using dictionary unions:
_ = {**x}
_ = x | y
_ = x | y
_ = x | {"a": 1}
_ = x | {"a": 1, "b": 2}
_ = x | y | {"a": 1, "b": 2}
Release v1.26.0
This release adds one new check and a bunch of bug fixes!
Add use-fluent-interface
(FURB184)
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
Thank you to @sbrugman for implementing this new check!
What's Changed
- Fix FURB183 false positive when using custom formatter by @dosisod in #315
- Fix FURB118
operator.contains
false positive, improve error messages by @dosisod in #317 - Initial implementation for fluid interface check by @sbrugman in #287
- Fix: false negative in FURB111 by @sbrugman in #313
- Fix FURB115 being emitted when using
len(x)
with non-boolean operators by @dosisod in #319
New Contributors
Full Changelog: v1.25.0...v1.26.0
Release v1.25.0
This release adds colorized output to Refurb, as well as some bugfixes!
Color output is enabled by default. If you don't like it you can disable it using any of the following methods:
- Use the
--no-color
CLI arg - Use
color = false
in the config file - Set the
NO_COLOR
env var
What's Changed
Full Changelog: v1.24.0...v1.25.0
Release v1.24.0
This release adds 2 new checks, some documentation updates, and allows for using Refurb with the new 1.7.0 release of Mypy. Thank you @bzoracler for fixing this!
Unpin Mypy v1.7.0
Mypy v1.7.0 made an internal change that broke Refurb (see #305). A workaround has since been found meaning we no longer need to pin to Mypy <1.7.0. If you are still experiencing any issues with using older/newer versions of Mypy with Refurb, please open an issue!
Add use-hexdigest-hashlib
check (FURB181)
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()
Add simplify-hashlib-ctor
(FURB182)
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")
What's Changed
- Fix incorrect grammar in README by @tylerlaprade in #307
- Specify what Python version Refurb uses when version isn't set by @dosisod in #308
- Experimental/use standalone visitor by @bzoracler in #309
New Contributors
- @tylerlaprade made their first contribution in #307
- @bzoracler made their first contribution in #309
Full Changelog: v1.23.0...v1.24.0
Release v1.23.0
This is a minor patch that pins Mypy to version <1.7.0, see #305 for more info.
What's Changed
Full Changelog: v1.22.2...v1.23.0
Release v1.22.2
This is a bug fix release that fixes crashes on certain files.
What's Changed
Full Changelog: v1.22.1...v1.22.2
v1.22.1
v1.22.0
This version adds 2 new checks, some bug fixes, and documentation fixes.
Improved Error Messages
Previously Refurb would assume certain identifiers such as Path
where imported as Path
, not pathlib.Path
. This made the context of error messages less helpful since they did not reflect what was actually in the source. Now more checks will use the more accurate pathlib.Path
identifier if needed.
Add use-chain-from-iterable
check (FURB179)
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(*x)
may be marginally faster/slower depending on the length ofx
. Since*
might potentially expand to a lot of arguments, it is better to usechain.from_iterable()
when you are unsure.
Add use-abc-shorthand
check (FURB180)
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
What's Changed
- Don't emit FURB120 when deleting default arg would change semantics by @dosisod in #289
- Update docs for FURB107 (
use-suppress
) by @dosisod in #292 - Add
use-chain-from-iterable
check by @dosisod in #293 - Tweak versioning docs, bump packages by @dosisod in #294
- Fix failing test in Mypy 1.6.0 by @dosisod in #296
- Bump packages, bump version by @dosisod in #297
Full Changelog: v1.21.0...v1.22.0