An abstract syntax tree for Python with inference support.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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.
Foundation classes that represent runtime objects during static analysis.
class BaseInstance:
"""
Base class for all runtime object representations.
Provides common functionality for objects that can be
inferred during static analysis but represent runtime entities.
"""
def __init__(self, instance_type: NodeNG) -> None:
"""Initialize with the type that created this instance."""
def infer(self, context: InferenceContext | None = None) -> Iterator[InferenceResult]:
"""
Infer this instance.
Parameters:
- context: Inference context
Yields:
This instance (instances infer to themselves)
"""
def getattr(self, name: str, context: InferenceContext | None = None) -> list[NodeNG]:
"""
Get attribute values for this instance.
Parameters:
- name: Attribute name to look up
- context: Inference context
Returns:
List of possible attribute values
Raises:
AttributeInferenceError: If attribute cannot be found
"""
def igetattr(self, name: str, context: InferenceContext | None = None) -> Iterator[InferenceResult]:
"""
Iterate over possible attribute values.
Parameters:
- name: Attribute name
- context: Inference context
Yields:
Possible attribute values
"""
class Instance(BaseInstance):
"""
Represents an instance of a class.
This is used when astroid can determine that a variable
holds an instance of a specific class, allowing for
method resolution and attribute access.
"""
def __init__(self, klass: ClassDef) -> None:
"""Initialize with the class this is an instance of."""
def getattr(self, name: str, context: InferenceContext | None = None) -> list[NodeNG]:
"""
Get attribute from the instance or its class.
Follows Python's attribute lookup order:
1. Instance attributes
2. Class attributes
3. Parent class attributes (via MRO)
4. Descriptors and properties
Parameters:
- name: Attribute name
- context: Inference context
Returns:
List of possible attribute values
"""
def has_dynamic_getattr(self) -> bool:
"""Check if the class has __getattr__ or __getattribute__."""
def _proxied(self) -> ClassDef:
"""Get the class this is an instance of."""Classes representing bound and unbound methods in the static analysis context.
class UnboundMethod:
"""
Represents an unbound method.
An unbound method is a function defined in a class
but not yet bound to a specific instance.
"""
def __init__(self, proxy: FunctionDef) -> None:
"""Initialize with the function this represents."""
def infer(self, context: InferenceContext | None = None) -> Iterator[UnboundMethod]:
"""Infer this unbound method (returns self)."""
def getattr(self, name: str, context: InferenceContext | None = None) -> list[NodeNG]:
"""Get attributes from the underlying function."""
@property
def name(self) -> str:
"""Get the method name."""
@property
def parent(self) -> ClassDef:
"""Get the class containing this method."""
class BoundMethod:
"""
Represents a method bound to a specific instance.
A bound method is created when accessing a method
on an instance (e.g., obj.method).
"""
def __init__(self, proxy: UnboundMethod, bound: Instance) -> None:
"""
Initialize with unbound method and bound instance.
Parameters:
- proxy: The unbound method
- bound: The instance this is bound to
"""
bound: Instance
"""The instance this method is bound to."""
proxy: UnboundMethod
"""The underlying unbound method."""
def infer(self, context: InferenceContext | None = None) -> Iterator[BoundMethod]:
"""Infer this bound method (returns self)."""
def infer_call_result(self, context: InferenceContext | None = None, **kwargs) -> Iterator[InferenceResult]:
"""
Infer the result of calling this bound method.
Parameters:
- context: Inference context
- kwargs: Additional call arguments
Yields:
Possible return values of the method call
"""
@property
def name(self) -> str:
"""Get the method name."""
@property
def qname(self) -> str:
"""Get the qualified method name."""Classes representing generator and iterator objects.
class Generator(Instance):
"""
Represents a generator object.
Created when a function contains yield statements,
representing the generator object returned by calling
such a function.
"""
def __init__(self, func: FunctionDef) -> None:
"""Initialize with the generator function."""
def infer(self, context: InferenceContext | None = None) -> Iterator[Generator]:
"""Infer this generator (returns self)."""
class AsyncGenerator(Generator):
"""
Represents an async generator object.
Created when an async function contains yield statements.
"""Base classes for wrapping and proxying other objects.
class Proxy:
"""
Base class for proxy objects.
Proxies delegate attribute access and method calls
to an underlying object while potentially modifying
or filtering the behavior.
"""
def __init__(self, proxied: Any) -> None:
"""Initialize with the object to proxy."""
def __getattr__(self, name: str) -> Any:
"""Delegate attribute access to proxied object."""
def infer(self, context: InferenceContext | None = None) -> Iterator[InferenceResult]:
"""Infer the proxied object."""import astroid
code = '''
class MyClass:
class_attr = "class_value"
def __init__(self):
self.instance_attr = "instance_value"
def method(self):
return self.instance_attr
obj = MyClass()
value = obj.instance_attr
result = obj.method()
'''
module = astroid.parse(code)
# Find the assignment to obj
for assign in module.nodes_of_class(astroid.Assign):
if assign.targets[0].name == 'obj':
# Infer the assigned value
for inferred in assign.value.infer():
if isinstance(inferred, astroid.Instance):
print(f"obj is an instance of {inferred._proxied.name}")
# Get instance attributes
try:
attrs = inferred.getattr('instance_attr')
print(f"instance_attr: {[attr.value for attr in attrs if hasattr(attr, 'value')]}")
except astroid.AttributeInferenceError:
print("instance_attr not found")import astroid
code = '''
class Calculator:
def add(self, a, b):
return a + b
def multiply(self, x, y):
return x * y
calc = Calculator()
add_method = calc.add
result = calc.add(5, 3)
'''
module = astroid.parse(code)
# Find method access
for attr in module.nodes_of_class(astroid.Attribute):
if attr.attrname in ('add', 'multiply'):
for inferred in attr.infer():
if isinstance(inferred, astroid.BoundMethod):
print(f"Found bound method: {inferred.name}")
print(f"Bound to: {type(inferred.bound).__name__}")
print(f"Underlying function: {inferred.proxy.name}")
# Find method calls
for call in module.nodes_of_class(astroid.Call):
if isinstance(call.func, astroid.Attribute):
for inferred in call.func.infer():
if isinstance(inferred, astroid.BoundMethod):
# Try to infer call result
try:
for result in inferred.infer_call_result():
print(f"Method call result: {result}")
except astroid.InferenceError:
print("Cannot infer method call result")import astroid
code = '''
def number_generator():
for i in range(3):
yield i
def async_generator():
for i in range(3):
yield i
gen = number_generator()
'''
module = astroid.parse(code)
# Find generator creation
for assign in module.nodes_of_class(astroid.Assign):
if assign.targets[0].name == 'gen':
for inferred in assign.value.infer():
if isinstance(inferred, astroid.Generator):
print(f"Found generator from function: {inferred._proxied.name}")import astroid
code = '''
class DynamicClass:
def __init__(self):
self.data = {}
def __getattr__(self, name):
return self.data.get(name, "default")
def __setattr__(self, name, value):
if name == 'data':
super().__setattr__(name, value)
else:
self.data[name] = value
obj = DynamicClass()
obj.dynamic_attr = "value"
result = obj.dynamic_attr
'''
module = astroid.parse(code)
# Find the DynamicClass instance
for assign in module.nodes_of_class(astroid.Assign):
if assign.targets[0].name == 'obj':
for inferred in assign.value.infer():
if isinstance(inferred, astroid.Instance):
# Check for dynamic attribute behavior
has_getattr = inferred.has_dynamic_getattr()
print(f"Has dynamic getattr: {has_getattr}")
# Try to access dynamic attributes
try:
dynamic_attrs = inferred.getattr('dynamic_attr')
print(f"Dynamic attribute found: {len(dynamic_attrs)} possibilities")
except astroid.AttributeInferenceError:
print("Dynamic attribute access not resolved")import astroid
class CustomInstance(astroid.Instance):
"""Custom instance with special behavior."""
def getattr(self, name, context=None):
# Custom attribute resolution logic
if name.startswith('special_'):
# Return special attributes
return [astroid.Const(value=f"Special: {name}")]
# Delegate to parent for normal attributes
return super().getattr(name, context)
# Register custom instance behavior
def create_custom_instance(klass):
"""Create custom instance instead of regular instance."""
return CustomInstance(klass)
# This would require deeper integration with astroid's inference systemimport astroid
class LoggingProxy(astroid.Proxy):
"""Proxy that logs attribute access."""
def __init__(self, proxied):
super().__init__(proxied)
self.access_log = []
def getattr(self, name, context=None):
self.access_log.append(name)
return self._proxied.getattr(name, context)
def log_report(self):
return f"Accessed attributes: {self.access_log}"import astroid
def analyze_method_binding(module):
"""Analyze method binding in a module."""
method_info = []
for attr in module.nodes_of_class(astroid.Attribute):
try:
for inferred in attr.infer():
if isinstance(inferred, astroid.BoundMethod):
info = {
'method_name': inferred.name,
'class_name': inferred.bound._proxied.name,
'is_bound': True
}
method_info.append(info)
elif isinstance(inferred, astroid.UnboundMethod):
info = {
'method_name': inferred.name,
'class_name': inferred.parent.name,
'is_bound': False
}
method_info.append(info)
except astroid.InferenceError:
continue
return method_infoThe base classes integrate tightly with astroid's inference system:
These classes provide the foundation for astroid's sophisticated understanding of Python object behavior during static analysis.
Install with Tessl CLI
npx tessl i tessl/pypi-astroid