0
# Base Classes and Objects
1
2
Foundation classes for instances, methods, and other inferred objects that represent runtime Python objects within the static analysis context. These classes bridge the gap between static AST nodes and runtime object behavior.
3
4
## Capabilities
5
6
### Base Instance Classes
7
8
Foundation classes that represent runtime objects during static analysis.
9
10
```python { .api }
11
class BaseInstance:
12
"""
13
Base class for all runtime object representations.
14
15
Provides common functionality for objects that can be
16
inferred during static analysis but represent runtime entities.
17
"""
18
19
def __init__(self, instance_type: NodeNG) -> None:
20
"""Initialize with the type that created this instance."""
21
22
def infer(self, context: InferenceContext | None = None) -> Iterator[InferenceResult]:
23
"""
24
Infer this instance.
25
26
Parameters:
27
- context: Inference context
28
29
Yields:
30
This instance (instances infer to themselves)
31
"""
32
33
def getattr(self, name: str, context: InferenceContext | None = None) -> list[NodeNG]:
34
"""
35
Get attribute values for this instance.
36
37
Parameters:
38
- name: Attribute name to look up
39
- context: Inference context
40
41
Returns:
42
List of possible attribute values
43
44
Raises:
45
AttributeInferenceError: If attribute cannot be found
46
"""
47
48
def igetattr(self, name: str, context: InferenceContext | None = None) -> Iterator[InferenceResult]:
49
"""
50
Iterate over possible attribute values.
51
52
Parameters:
53
- name: Attribute name
54
- context: Inference context
55
56
Yields:
57
Possible attribute values
58
"""
59
60
class Instance(BaseInstance):
61
"""
62
Represents an instance of a class.
63
64
This is used when astroid can determine that a variable
65
holds an instance of a specific class, allowing for
66
method resolution and attribute access.
67
"""
68
69
def __init__(self, klass: ClassDef) -> None:
70
"""Initialize with the class this is an instance of."""
71
72
def getattr(self, name: str, context: InferenceContext | None = None) -> list[NodeNG]:
73
"""
74
Get attribute from the instance or its class.
75
76
Follows Python's attribute lookup order:
77
1. Instance attributes
78
2. Class attributes
79
3. Parent class attributes (via MRO)
80
4. Descriptors and properties
81
82
Parameters:
83
- name: Attribute name
84
- context: Inference context
85
86
Returns:
87
List of possible attribute values
88
"""
89
90
def has_dynamic_getattr(self) -> bool:
91
"""Check if the class has __getattr__ or __getattribute__."""
92
93
def _proxied(self) -> ClassDef:
94
"""Get the class this is an instance of."""
95
```
96
97
### Method Representations
98
99
Classes representing bound and unbound methods in the static analysis context.
100
101
```python { .api }
102
class UnboundMethod:
103
"""
104
Represents an unbound method.
105
106
An unbound method is a function defined in a class
107
but not yet bound to a specific instance.
108
"""
109
110
def __init__(self, proxy: FunctionDef) -> None:
111
"""Initialize with the function this represents."""
112
113
def infer(self, context: InferenceContext | None = None) -> Iterator[UnboundMethod]:
114
"""Infer this unbound method (returns self)."""
115
116
def getattr(self, name: str, context: InferenceContext | None = None) -> list[NodeNG]:
117
"""Get attributes from the underlying function."""
118
119
@property
120
def name(self) -> str:
121
"""Get the method name."""
122
123
@property
124
def parent(self) -> ClassDef:
125
"""Get the class containing this method."""
126
127
class BoundMethod:
128
"""
129
Represents a method bound to a specific instance.
130
131
A bound method is created when accessing a method
132
on an instance (e.g., obj.method).
133
"""
134
135
def __init__(self, proxy: UnboundMethod, bound: Instance) -> None:
136
"""
137
Initialize with unbound method and bound instance.
138
139
Parameters:
140
- proxy: The unbound method
141
- bound: The instance this is bound to
142
"""
143
144
bound: Instance
145
"""The instance this method is bound to."""
146
147
proxy: UnboundMethod
148
"""The underlying unbound method."""
149
150
def infer(self, context: InferenceContext | None = None) -> Iterator[BoundMethod]:
151
"""Infer this bound method (returns self)."""
152
153
def infer_call_result(self, context: InferenceContext | None = None, **kwargs) -> Iterator[InferenceResult]:
154
"""
155
Infer the result of calling this bound method.
156
157
Parameters:
158
- context: Inference context
159
- kwargs: Additional call arguments
160
161
Yields:
162
Possible return values of the method call
163
"""
164
165
@property
166
def name(self) -> str:
167
"""Get the method name."""
168
169
@property
170
def qname(self) -> str:
171
"""Get the qualified method name."""
172
```
173
174
### Generator and Iterator Objects
175
176
Classes representing generator and iterator objects.
177
178
```python { .api }
179
class Generator(Instance):
180
"""
181
Represents a generator object.
182
183
Created when a function contains yield statements,
184
representing the generator object returned by calling
185
such a function.
186
"""
187
188
def __init__(self, func: FunctionDef) -> None:
189
"""Initialize with the generator function."""
190
191
def infer(self, context: InferenceContext | None = None) -> Iterator[Generator]:
192
"""Infer this generator (returns self)."""
193
194
class AsyncGenerator(Generator):
195
"""
196
Represents an async generator object.
197
198
Created when an async function contains yield statements.
199
"""
200
```
201
202
### Proxy and Wrapper Classes
203
204
Base classes for wrapping and proxying other objects.
205
206
```python { .api }
207
class Proxy:
208
"""
209
Base class for proxy objects.
210
211
Proxies delegate attribute access and method calls
212
to an underlying object while potentially modifying
213
or filtering the behavior.
214
"""
215
216
def __init__(self, proxied: Any) -> None:
217
"""Initialize with the object to proxy."""
218
219
def __getattr__(self, name: str) -> Any:
220
"""Delegate attribute access to proxied object."""
221
222
def infer(self, context: InferenceContext | None = None) -> Iterator[InferenceResult]:
223
"""Infer the proxied object."""
224
```
225
226
## Usage Examples
227
228
### Working with Instances
229
230
```python
231
import astroid
232
233
code = '''
234
class MyClass:
235
class_attr = "class_value"
236
237
def __init__(self):
238
self.instance_attr = "instance_value"
239
240
def method(self):
241
return self.instance_attr
242
243
obj = MyClass()
244
value = obj.instance_attr
245
result = obj.method()
246
'''
247
248
module = astroid.parse(code)
249
250
# Find the assignment to obj
251
for assign in module.nodes_of_class(astroid.Assign):
252
if assign.targets[0].name == 'obj':
253
# Infer the assigned value
254
for inferred in assign.value.infer():
255
if isinstance(inferred, astroid.Instance):
256
print(f"obj is an instance of {inferred._proxied.name}")
257
258
# Get instance attributes
259
try:
260
attrs = inferred.getattr('instance_attr')
261
print(f"instance_attr: {[attr.value for attr in attrs if hasattr(attr, 'value')]}")
262
except astroid.AttributeInferenceError:
263
print("instance_attr not found")
264
```
265
266
### Method Binding and Calls
267
268
```python
269
import astroid
270
271
code = '''
272
class Calculator:
273
def add(self, a, b):
274
return a + b
275
276
def multiply(self, x, y):
277
return x * y
278
279
calc = Calculator()
280
add_method = calc.add
281
result = calc.add(5, 3)
282
'''
283
284
module = astroid.parse(code)
285
286
# Find method access
287
for attr in module.nodes_of_class(astroid.Attribute):
288
if attr.attrname in ('add', 'multiply'):
289
for inferred in attr.infer():
290
if isinstance(inferred, astroid.BoundMethod):
291
print(f"Found bound method: {inferred.name}")
292
print(f"Bound to: {type(inferred.bound).__name__}")
293
print(f"Underlying function: {inferred.proxy.name}")
294
295
# Find method calls
296
for call in module.nodes_of_class(astroid.Call):
297
if isinstance(call.func, astroid.Attribute):
298
for inferred in call.func.infer():
299
if isinstance(inferred, astroid.BoundMethod):
300
# Try to infer call result
301
try:
302
for result in inferred.infer_call_result():
303
print(f"Method call result: {result}")
304
except astroid.InferenceError:
305
print("Cannot infer method call result")
306
```
307
308
### Generator Objects
309
310
```python
311
import astroid
312
313
code = '''
314
def number_generator():
315
for i in range(3):
316
yield i
317
318
def async_generator():
319
for i in range(3):
320
yield i
321
322
gen = number_generator()
323
'''
324
325
module = astroid.parse(code)
326
327
# Find generator creation
328
for assign in module.nodes_of_class(astroid.Assign):
329
if assign.targets[0].name == 'gen':
330
for inferred in assign.value.infer():
331
if isinstance(inferred, astroid.Generator):
332
print(f"Found generator from function: {inferred._proxied.name}")
333
```
334
335
### Custom Instance Behavior
336
337
```python
338
import astroid
339
340
code = '''
341
class DynamicClass:
342
def __init__(self):
343
self.data = {}
344
345
def __getattr__(self, name):
346
return self.data.get(name, "default")
347
348
def __setattr__(self, name, value):
349
if name == 'data':
350
super().__setattr__(name, value)
351
else:
352
self.data[name] = value
353
354
obj = DynamicClass()
355
obj.dynamic_attr = "value"
356
result = obj.dynamic_attr
357
'''
358
359
module = astroid.parse(code)
360
361
# Find the DynamicClass instance
362
for assign in module.nodes_of_class(astroid.Assign):
363
if assign.targets[0].name == 'obj':
364
for inferred in assign.value.infer():
365
if isinstance(inferred, astroid.Instance):
366
# Check for dynamic attribute behavior
367
has_getattr = inferred.has_dynamic_getattr()
368
print(f"Has dynamic getattr: {has_getattr}")
369
370
# Try to access dynamic attributes
371
try:
372
dynamic_attrs = inferred.getattr('dynamic_attr')
373
print(f"Dynamic attribute found: {len(dynamic_attrs)} possibilities")
374
except astroid.AttributeInferenceError:
375
print("Dynamic attribute access not resolved")
376
```
377
378
## Advanced Usage
379
380
### Custom Instance Classes
381
382
```python
383
import astroid
384
385
class CustomInstance(astroid.Instance):
386
"""Custom instance with special behavior."""
387
388
def getattr(self, name, context=None):
389
# Custom attribute resolution logic
390
if name.startswith('special_'):
391
# Return special attributes
392
return [astroid.Const(value=f"Special: {name}")]
393
394
# Delegate to parent for normal attributes
395
return super().getattr(name, context)
396
397
# Register custom instance behavior
398
def create_custom_instance(klass):
399
"""Create custom instance instead of regular instance."""
400
return CustomInstance(klass)
401
402
# This would require deeper integration with astroid's inference system
403
```
404
405
### Proxy Implementation
406
407
```python
408
import astroid
409
410
class LoggingProxy(astroid.Proxy):
411
"""Proxy that logs attribute access."""
412
413
def __init__(self, proxied):
414
super().__init__(proxied)
415
self.access_log = []
416
417
def getattr(self, name, context=None):
418
self.access_log.append(name)
419
return self._proxied.getattr(name, context)
420
421
def log_report(self):
422
return f"Accessed attributes: {self.access_log}"
423
```
424
425
### Method Resolution
426
427
```python
428
import astroid
429
430
def analyze_method_binding(module):
431
"""Analyze method binding in a module."""
432
method_info = []
433
434
for attr in module.nodes_of_class(astroid.Attribute):
435
try:
436
for inferred in attr.infer():
437
if isinstance(inferred, astroid.BoundMethod):
438
info = {
439
'method_name': inferred.name,
440
'class_name': inferred.bound._proxied.name,
441
'is_bound': True
442
}
443
method_info.append(info)
444
elif isinstance(inferred, astroid.UnboundMethod):
445
info = {
446
'method_name': inferred.name,
447
'class_name': inferred.parent.name,
448
'is_bound': False
449
}
450
method_info.append(info)
451
except astroid.InferenceError:
452
continue
453
454
return method_info
455
```
456
457
## Integration with Inference
458
459
The base classes integrate tightly with astroid's inference system:
460
461
1. **Instance Creation**: When inferring class instantiation
462
2. **Method Binding**: When accessing methods on instances
463
3. **Attribute Resolution**: Following Python's attribute lookup rules
464
4. **Call Resolution**: Determining method call results
465
466
These classes provide the foundation for astroid's sophisticated understanding of Python object behavior during static analysis.