-
Notifications
You must be signed in to change notification settings - Fork 898
/
cached_instance_method.rs
106 lines (100 loc) · 3.09 KB
/
cached_instance_method.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
use ruff_python_ast::{self as ast, Decorator, Expr};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_semantic::SemanticModel;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for uses of the `functools.lru_cache` and `functools.cache`
/// decorators on methods.
///
/// ## Why is this bad?
/// Using the `functools.lru_cache` and `functools.cache` decorators on methods
/// can lead to memory leaks, as the global cache will retain a reference to
/// the instance, preventing it from being garbage collected.
///
/// Instead, refactor the method to depend only on its arguments and not on the
/// instance of the class, or use the `@lru_cache` decorator on a function
/// outside of the class.
///
/// ## Example
/// ```python
/// from functools import lru_cache
///
///
/// def square(x: int) -> int:
/// return x * x
///
///
/// class Number:
/// value: int
///
/// @lru_cache
/// def squared(self):
/// return square(self.value)
/// ```
///
/// Use instead:
/// ```python
/// from functools import lru_cache
///
///
/// @lru_cache
/// def square(x: int) -> int:
/// return x * x
///
///
/// class Number:
/// value: int
///
/// def squared(self):
/// return square(self.value)
/// ```
///
/// ## References
/// - [Python documentation: `functools.lru_cache`](https://docs.python.org/3/library/functools.html#functools.lru_cache)
/// - [Python documentation: `functools.cache`](https://docs.python.org/3/library/functools.html#functools.cache)
/// - [don't lru_cache methods!](https://www.youtube.com/watch?v=sVjtp6tGo0g)
#[violation]
pub struct CachedInstanceMethod;
impl Violation for CachedInstanceMethod {
#[derive_message_formats]
fn message(&self) -> String {
format!(
"Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks"
)
}
}
fn is_cache_func(expr: &Expr, semantic: &SemanticModel) -> bool {
semantic.resolve_call_path(expr).is_some_and(|call_path| {
matches!(call_path.as_slice(), ["functools", "lru_cache" | "cache"])
})
}
/// B019
pub(crate) fn cached_instance_method(checker: &mut Checker, decorator_list: &[Decorator]) {
if !checker.semantic().current_scope().kind.is_class() {
return;
}
for decorator in decorator_list {
// TODO(charlie): This should take into account `classmethod-decorators` and
// `staticmethod-decorators`.
if let Expr::Name(ast::ExprName { id, .. }) = &decorator.expression {
if id.as_str() == "classmethod" || id.as_str() == "staticmethod" {
return;
}
}
}
for decorator in decorator_list {
if is_cache_func(
match &decorator.expression {
Expr::Call(ast::ExprCall { func, .. }) => func,
_ => &decorator.expression,
},
checker.semantic(),
) {
checker
.diagnostics
.push(Diagnostic::new(CachedInstanceMethod, decorator.range()));
}
}
}