0
# Inference System
1
2
Advanced static inference capabilities that determine types and values of expressions. The inference system is one of astroid's key features, enabling static analysis tools to understand what Python code will do at runtime.
3
4
## Capabilities
5
6
### Inference Context
7
8
Manages state and caching during inference operations.
9
10
```python { .api }
11
class InferenceContext:
12
"""Context for managing inference state."""
13
14
nodes_inferred: int
15
"""Number of nodes inferred in this context."""
16
17
callcontext: CallContext | None
18
"""Call context for function arguments."""
19
20
boundnode: NodeNG | None
21
"""Node that this context is bound to."""
22
23
lookupname: str | None
24
"""Name being looked up."""
25
26
def __init__(self, nodes_inferred: int = 0) -> None:
27
"""Initialize inference context."""
28
29
def clone(self) -> InferenceContext:
30
"""Create a copy of this context."""
31
32
def is_empty(self) -> bool:
33
"""Check if context has no bound node."""
34
35
def push(self, node: NodeNG) -> InferenceContext:
36
"""Push a new node onto the context stack."""
37
38
def restore_path(self) -> None:
39
"""Restore the inference path."""
40
```
41
42
### Call Context
43
44
Specialized context for function and method calls.
45
46
```python { .api }
47
class CallContext:
48
"""Context for function/method calls."""
49
50
args: list[NodeNG]
51
"""Positional arguments."""
52
53
keywords: list[Keyword]
54
"""Keyword arguments."""
55
56
callee: NodeNG | None
57
"""The callable being invoked."""
58
59
def __init__(self, args: list[NodeNG], keywords: list[Keyword] | None = None, callee: NodeNG | None = None) -> None:
60
"""Initialize call context."""
61
62
def infer_argument(self, name: str, context: InferenceContext | None = None) -> Iterator[InferenceResult]:
63
"""Infer the value of a named argument."""
64
```
65
66
### Inference Functions
67
68
Core functions for performing inference on various node types.
69
70
```python { .api }
71
def infer_call_result(call_node: Call, context: InferenceContext | None = None) -> Iterator[InferenceResult]:
72
"""
73
Infer the result of a function/method call.
74
75
Parameters:
76
- call_node: Call node to infer
77
- context: Inference context
78
79
Yields:
80
Possible return values of the call
81
82
Raises:
83
InferenceError: When call cannot be inferred
84
"""
85
86
def infer_attribute(attr_node: Attribute, context: InferenceContext | None = None) -> Iterator[InferenceResult]:
87
"""
88
Infer attribute access results.
89
90
Parameters:
91
- attr_node: Attribute node to infer
92
- context: Inference context
93
94
Yields:
95
Possible attribute values
96
"""
97
98
def infer_subscript(subscript_node: Subscript, context: InferenceContext | None = None) -> Iterator[InferenceResult]:
99
"""
100
Infer subscription results.
101
102
Parameters:
103
- subscript_node: Subscript node to infer
104
- context: Inference context
105
106
Yields:
107
Possible subscripted values
108
"""
109
```
110
111
### Inference Tips
112
113
System for registering custom inference behavior for specific functions or classes.
114
115
```python { .api }
116
def inference_tip(func: Callable) -> Callable:
117
"""
118
Decorator to register inference tips.
119
120
Parameters:
121
- func: Function returning inference results
122
123
Returns:
124
Decorated function that can be used as inference tip
125
"""
126
127
def _inference_tip_cached(func: Callable) -> Callable:
128
"""
129
Cached version of inference tip decorator.
130
131
Parameters:
132
- func: Function to cache inference results for
133
134
Returns:
135
Cached inference function
136
"""
137
```
138
139
### Special Inference Objects
140
141
Objects representing special inference states and results.
142
143
```python { .api }
144
class Uninferable:
145
"""Represents values that cannot be inferred."""
146
147
def __bool__(self) -> bool:
148
"""Always returns False."""
149
150
def __repr__(self) -> str:
151
"""String representation."""
152
153
Uninferable: type[Uninferable]
154
"""Singleton representing uninferable values."""
155
156
def safe_infer(node: NodeNG, context: InferenceContext | None = None) -> NodeNG | None:
157
"""
158
Safe inference that returns None instead of raising exceptions.
159
160
Parameters:
161
- node: Node to infer
162
- context: Inference context
163
164
Returns:
165
Inferred node or None if inference fails
166
"""
167
```
168
169
## Usage Examples
170
171
### Basic Inference
172
173
```python
174
import astroid
175
176
code = '''
177
x = 42
178
y = "hello"
179
z = [1, 2, 3]
180
result = x + len(y)
181
'''
182
183
module = astroid.parse(code)
184
185
# Infer variable values
186
for node in module.nodes_of_class(astroid.Name):
187
if node.name == 'result':
188
for inferred in node.infer():
189
if hasattr(inferred, 'value'):
190
print(f"Result value: {inferred.value}") # 47
191
```
192
193
### Function Call Inference
194
195
```python
196
import astroid
197
198
code = '''
199
def add(a, b):
200
return a + b
201
202
def multiply(x, y):
203
return x * y
204
205
result1 = add(10, 20)
206
result2 = multiply(3, 4)
207
'''
208
209
module = astroid.parse(code)
210
211
# Find function calls and infer results
212
for call in module.nodes_of_class(astroid.Call):
213
try:
214
for inferred in call.infer():
215
if hasattr(inferred, 'value'):
216
print(f"Call result: {inferred.value}")
217
except astroid.InferenceError:
218
print("Cannot infer call result")
219
```
220
221
### Attribute Inference
222
223
```python
224
import astroid
225
226
code = '''
227
class MyClass:
228
def __init__(self):
229
self.value = 42
230
231
def get_value(self):
232
return self.value
233
234
obj = MyClass()
235
attr_value = obj.value
236
method_result = obj.get_value()
237
'''
238
239
module = astroid.parse(code)
240
241
# Infer attribute access
242
for attr in module.nodes_of_class(astroid.Attribute):
243
try:
244
for inferred in attr.infer():
245
print(f"Attribute {attr.attrname}: {type(inferred).__name__}")
246
except astroid.InferenceError:
247
print(f"Cannot infer attribute {attr.attrname}")
248
```
249
250
### Context-Aware Inference
251
252
```python
253
import astroid
254
255
code = '''
256
def func(param):
257
if isinstance(param, str):
258
return param.upper()
259
elif isinstance(param, int):
260
return param * 2
261
return None
262
263
result = func("hello")
264
'''
265
266
module = astroid.parse(code)
267
268
# Create inference context
269
context = astroid.InferenceContext()
270
271
# Find the function call
272
for call in module.nodes_of_class(astroid.Call):
273
if isinstance(call.func, astroid.Name) and call.func.name == 'func':
274
try:
275
for inferred in call.infer(context):
276
print(f"Function result: {inferred}")
277
except astroid.InferenceError as e:
278
print(f"Inference failed: {e}")
279
```
280
281
### Custom Inference Tips
282
283
```python
284
import astroid
285
286
# Register custom inference for a function
287
@astroid.inference_tip
288
def infer_custom_function(node, context=None):
289
"""Custom inference for special functions."""
290
if (isinstance(node, astroid.Call) and
291
isinstance(node.func, astroid.Name) and
292
node.func.name == 'special_func'):
293
# Return custom inference result
294
return iter([astroid.Const(value="custom_result")])
295
raise astroid.InferenceError("Not a special function")
296
297
# Apply to manager
298
astroid.MANAGER.register_transform(astroid.Call, infer_custom_function)
299
```
300
301
### Working with Uninferable
302
303
```python
304
import astroid
305
306
code = '''
307
import random
308
x = random.choice([1, 2, 3]) # Can't be statically determined
309
y = x + 10
310
'''
311
312
module = astroid.parse(code)
313
314
for name in module.nodes_of_class(astroid.Name):
315
if name.name in ('x', 'y'):
316
inferred = astroid.safe_infer(name)
317
if inferred is None:
318
print(f"{name.name} is uninferable")
319
elif inferred is astroid.Uninferable:
320
print(f"{name.name} explicitly uninferable")
321
else:
322
print(f"{name.name} inferred as: {inferred}")
323
```
324
325
## Advanced Inference
326
327
### Method Resolution
328
329
```python
330
import astroid
331
332
code = '''
333
class Base:
334
def method(self):
335
return "base"
336
337
class Derived(Base):
338
def method(self):
339
return "derived"
340
341
obj = Derived()
342
result = obj.method()
343
'''
344
345
module = astroid.parse(code)
346
347
# Find method calls and infer through MRO
348
for call in module.nodes_of_class(astroid.Call):
349
if isinstance(call.func, astroid.Attribute):
350
try:
351
for inferred in call.infer():
352
print(f"Method result: {inferred}")
353
except astroid.InferenceError:
354
print("Method call inference failed")
355
```
356
357
### Inference Limitations
358
359
The inference system has limitations:
360
361
- Dynamic attribute creation
362
- Runtime-dependent values
363
- Complex control flow
364
- Metaclass behavior
365
- Import system intricacies
366
367
```python
368
import astroid
369
370
# These cases may not infer correctly
371
problematic_code = '''
372
# Dynamic attributes
373
class Dynamic:
374
pass
375
376
obj = Dynamic()
377
setattr(obj, 'attr', 42) # Can't infer obj.attr
378
379
# Runtime values
380
import random
381
value = random.randint(1, 100) # Uninferable
382
383
# Complex control flow
384
def complex_func(x):
385
for i in range(x):
386
if some_condition():
387
return compute_value(i)
388
return None
389
'''
390
```
391
392
## Error Handling
393
394
Inference operations can raise various exceptions:
395
396
- **InferenceError**: Base inference failure
397
- **NameInferenceError**: Name lookup failure
398
- **AttributeInferenceError**: Attribute access failure
399
- **UseInferenceDefault**: Fallback to default behavior
400
401
Always handle these exceptions or use `safe_infer()` for robust code.