An abstract syntax tree for Python with inference support.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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.
Manages state and caching during inference operations.
class InferenceContext:
"""Context for managing inference state."""
nodes_inferred: int
"""Number of nodes inferred in this context."""
callcontext: CallContext | None
"""Call context for function arguments."""
boundnode: NodeNG | None
"""Node that this context is bound to."""
lookupname: str | None
"""Name being looked up."""
def __init__(self, nodes_inferred: int = 0) -> None:
"""Initialize inference context."""
def clone(self) -> InferenceContext:
"""Create a copy of this context."""
def is_empty(self) -> bool:
"""Check if context has no bound node."""
def push(self, node: NodeNG) -> InferenceContext:
"""Push a new node onto the context stack."""
def restore_path(self) -> None:
"""Restore the inference path."""Specialized context for function and method calls.
class CallContext:
"""Context for function/method calls."""
args: list[NodeNG]
"""Positional arguments."""
keywords: list[Keyword]
"""Keyword arguments."""
callee: NodeNG | None
"""The callable being invoked."""
def __init__(self, args: list[NodeNG], keywords: list[Keyword] | None = None, callee: NodeNG | None = None) -> None:
"""Initialize call context."""
def infer_argument(self, name: str, context: InferenceContext | None = None) -> Iterator[InferenceResult]:
"""Infer the value of a named argument."""Core functions for performing inference on various node types.
def infer_call_result(call_node: Call, context: InferenceContext | None = None) -> Iterator[InferenceResult]:
"""
Infer the result of a function/method call.
Parameters:
- call_node: Call node to infer
- context: Inference context
Yields:
Possible return values of the call
Raises:
InferenceError: When call cannot be inferred
"""
def infer_attribute(attr_node: Attribute, context: InferenceContext | None = None) -> Iterator[InferenceResult]:
"""
Infer attribute access results.
Parameters:
- attr_node: Attribute node to infer
- context: Inference context
Yields:
Possible attribute values
"""
def infer_subscript(subscript_node: Subscript, context: InferenceContext | None = None) -> Iterator[InferenceResult]:
"""
Infer subscription results.
Parameters:
- subscript_node: Subscript node to infer
- context: Inference context
Yields:
Possible subscripted values
"""System for registering custom inference behavior for specific functions or classes.
def inference_tip(func: Callable) -> Callable:
"""
Decorator to register inference tips.
Parameters:
- func: Function returning inference results
Returns:
Decorated function that can be used as inference tip
"""
def _inference_tip_cached(func: Callable) -> Callable:
"""
Cached version of inference tip decorator.
Parameters:
- func: Function to cache inference results for
Returns:
Cached inference function
"""Objects representing special inference states and results.
class Uninferable:
"""Represents values that cannot be inferred."""
def __bool__(self) -> bool:
"""Always returns False."""
def __repr__(self) -> str:
"""String representation."""
Uninferable: type[Uninferable]
"""Singleton representing uninferable values."""
def safe_infer(node: NodeNG, context: InferenceContext | None = None) -> NodeNG | None:
"""
Safe inference that returns None instead of raising exceptions.
Parameters:
- node: Node to infer
- context: Inference context
Returns:
Inferred node or None if inference fails
"""import astroid
code = '''
x = 42
y = "hello"
z = [1, 2, 3]
result = x + len(y)
'''
module = astroid.parse(code)
# Infer variable values
for node in module.nodes_of_class(astroid.Name):
if node.name == 'result':
for inferred in node.infer():
if hasattr(inferred, 'value'):
print(f"Result value: {inferred.value}") # 47import astroid
code = '''
def add(a, b):
return a + b
def multiply(x, y):
return x * y
result1 = add(10, 20)
result2 = multiply(3, 4)
'''
module = astroid.parse(code)
# Find function calls and infer results
for call in module.nodes_of_class(astroid.Call):
try:
for inferred in call.infer():
if hasattr(inferred, 'value'):
print(f"Call result: {inferred.value}")
except astroid.InferenceError:
print("Cannot infer call result")import astroid
code = '''
class MyClass:
def __init__(self):
self.value = 42
def get_value(self):
return self.value
obj = MyClass()
attr_value = obj.value
method_result = obj.get_value()
'''
module = astroid.parse(code)
# Infer attribute access
for attr in module.nodes_of_class(astroid.Attribute):
try:
for inferred in attr.infer():
print(f"Attribute {attr.attrname}: {type(inferred).__name__}")
except astroid.InferenceError:
print(f"Cannot infer attribute {attr.attrname}")import astroid
code = '''
def func(param):
if isinstance(param, str):
return param.upper()
elif isinstance(param, int):
return param * 2
return None
result = func("hello")
'''
module = astroid.parse(code)
# Create inference context
context = astroid.InferenceContext()
# Find the function call
for call in module.nodes_of_class(astroid.Call):
if isinstance(call.func, astroid.Name) and call.func.name == 'func':
try:
for inferred in call.infer(context):
print(f"Function result: {inferred}")
except astroid.InferenceError as e:
print(f"Inference failed: {e}")import astroid
# Register custom inference for a function
@astroid.inference_tip
def infer_custom_function(node, context=None):
"""Custom inference for special functions."""
if (isinstance(node, astroid.Call) and
isinstance(node.func, astroid.Name) and
node.func.name == 'special_func'):
# Return custom inference result
return iter([astroid.Const(value="custom_result")])
raise astroid.InferenceError("Not a special function")
# Apply to manager
astroid.MANAGER.register_transform(astroid.Call, infer_custom_function)import astroid
code = '''
import random
x = random.choice([1, 2, 3]) # Can't be statically determined
y = x + 10
'''
module = astroid.parse(code)
for name in module.nodes_of_class(astroid.Name):
if name.name in ('x', 'y'):
inferred = astroid.safe_infer(name)
if inferred is None:
print(f"{name.name} is uninferable")
elif inferred is astroid.Uninferable:
print(f"{name.name} explicitly uninferable")
else:
print(f"{name.name} inferred as: {inferred}")import astroid
code = '''
class Base:
def method(self):
return "base"
class Derived(Base):
def method(self):
return "derived"
obj = Derived()
result = obj.method()
'''
module = astroid.parse(code)
# Find method calls and infer through MRO
for call in module.nodes_of_class(astroid.Call):
if isinstance(call.func, astroid.Attribute):
try:
for inferred in call.infer():
print(f"Method result: {inferred}")
except astroid.InferenceError:
print("Method call inference failed")The inference system has limitations:
import astroid
# These cases may not infer correctly
problematic_code = '''
# Dynamic attributes
class Dynamic:
pass
obj = Dynamic()
setattr(obj, 'attr', 42) # Can't infer obj.attr
# Runtime values
import random
value = random.randint(1, 100) # Uninferable
# Complex control flow
def complex_func(x):
for i in range(x):
if some_condition():
return compute_value(i)
return None
'''Inference operations can raise various exceptions:
Always handle these exceptions or use safe_infer() for robust code.
Install with Tessl CLI
npx tessl i tessl/pypi-astroid