Description
Summary
Ruff's handling of B008 is different from the flake8-bugbear behavior, in that it only catches cases where the default argument is a function AND the type is not annotated as something immutable.
On the other hand, flake8-bugbear catches any function that is called as a default argument, which catches more potential problematic code.
Here's a silly example:
import random
def get_random_number(num: int = random.randint(1, 100)) -> int:
return num
Ruff's behavior
uvx ruff check --select B008 foo.py
All checks passed!
Flake8-bugbear's behavior
uvx --with flake8-bugbear flake8 --select B008 foo.py
foo.py:3:34: B008 Do not perform function calls in argument defaults. The call is performed only once at function definition time. All calls to your function will reuse the result of that definition-time function call. If this is intended, assign the function call to a module-level variable and use that variable as a default value.
While I understand that keeping the default behavior as-is might be preferable in many cases, there are also many cases where the return value of a function is not static, even if the return type is, and this can cause unintended issues. Here's a recent example that came up in one of the codebases I help maintain.
def log_message(message: str, username: str = get_current_username()):
...
In this case, there is a bug in this code when different users use this code running on the same server. Flake8-bugbear's default behavior would flag this, but ruff's implementation does not because the annotated type is str
.
I'm not sure the best way to name such an option, but I would like to suggest adding a setting like
[tool.ruff.lint.flake8-bugbear]
# Don't allow any function calls in default arguments, even if they return immutable types
dont-allow-any-function-defaults = true