or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

bases.mdexceptions.mdindex.mdinference.mdmanager.mdnodes.mdparsing.md

bases.mddocs/

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.