CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-astroid

An abstract syntax tree for Python with inference support.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

inference.mddocs/

Inference System

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.

Capabilities

Inference Context

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."""

Call Context

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."""

Inference Functions

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
    """

Inference Tips

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
    """

Special Inference Objects

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
    """

Usage Examples

Basic Inference

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}")  # 47

Function Call Inference

import 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")

Attribute Inference

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}")

Context-Aware Inference

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}")

Custom Inference Tips

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)

Working with Uninferable

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}")

Advanced Inference

Method Resolution

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")

Inference Limitations

The inference system has limitations:

  • Dynamic attribute creation
  • Runtime-dependent values
  • Complex control flow
  • Metaclass behavior
  • Import system intricacies
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
'''

Error Handling

Inference operations can raise various exceptions:

  • InferenceError: Base inference failure
  • NameInferenceError: Name lookup failure
  • AttributeInferenceError: Attribute access failure
  • UseInferenceDefault: Fallback to default behavior

Always handle these exceptions or use safe_infer() for robust code.

Install with Tessl CLI

npx tessl i tessl/pypi-astroid

docs

bases.md

exceptions.md

index.md

inference.md

manager.md

nodes.md

parsing.md

tile.json