Skip to main content

A Python module to simulate recursive function calls using iteration, providing explicit control over execution flow and avoiding stack overflow issues.

Project description

iterativerecursion

iterativerecursion banner

CI PyPI version Python versions License: MIT DeepWiki

A Python module to simulate recursive function calls using iteration, providing explicit control over execution flow and avoiding stack overflow issues.

Overview

iterativerecursion provides a mechanism to chain function calls iteratively while maintaining a recursive-like pattern. Instead of relying on the call stack, functions explicitly declare what to call next, making the execution flow transparent and controllable.

Why Use This?

  • Avoid Stack Overflow: Handle deep recursion without hitting Python's recursion limit
  • Explicit Control: See and control the exact flow of function calls
  • Debugging: Easier to trace execution without deep call stacks
  • State Management: Shared environment for passing data between functions
  • Safety: Built-in iteration limits to prevent infinite loops

When to Use This

This library is useful when you need:

  • Deep recursion that exceeds Python's stack limit (~1000 calls)
  • Explicit control over recursive execution flow
  • To convert recursive algorithms to iterative ones systematically
  • State machines or complex control flow patterns

Scope & limitations

This engine is essentially a trampoline / dispatcher: each step returns the name of the next function to run. That makes it a great fit for tail recursion (linear step-by-step recursion), workflows, and state machines.

It does not automatically emulate a full call stack. Patterns that require “returning to the caller” (e.g. tree recursion like fib(n-1) + fib(n-2), post-order DFS reductions) require you to model an explicit stack/frames (continuations) inside environment_variables.

For most cases, normal recursion is simpler and preferred. Use this when recursion depth or explicit control becomes a concern.

Installation

Using uv

uv add iterativerecursion

Using pip

pip install iterativerecursion

Quick Start

from iterativerecursion import IterativeRecursionEngine, FunctionReturn

def greet(name: str) -> FunctionReturn:
    print(f"Hello, {name}!")
    return FunctionReturn(
        returned_values={"next_name": "World"},
        next_function_to_call="farewell",
        arg_env_mapping={"name": "next_name"}
    )

def farewell(name: str) -> FunctionReturn:
    print(f"Goodbye, {name}!")
    return FunctionReturn(
        returned_values={}
        # next_function_to_call defaults to None to terminate
    )

# Create engine and register functions
engine = IterativeRecursionEngine()
engine.add_function(greet)
engine.add_function(farewell)

# Start execution
engine.start_function_caller(
    next_function_to_call="greet",
    environment_variables={"initial_name": "Alice"},
    arg_env_mapping={"name": "initial_name"}
)

Output:

Hello, Alice!
Goodbye, World!

How It Works

Functions return a FunctionReturn dataclass instance with three attributes:

Attribute Type Description
returned_values dict[str, Any] Values to store in the shared environment
next_function_to_call str | None Name of the next function to execute, or None to stop (default: None)
arg_env_mapping dict[str, str] Mapping of parameter names to environment variable keys (default: auto-mapped from returned_values keys)

Key Feature: If arg_env_mapping is not specified, it automatically maps each key in returned_values to itself. For example, {"counter": 5} automatically creates {"counter": "counter"} mapping.

The engine maintains a shared environment where functions can store and retrieve values across calls.

Examples

Using the @register Decorator

The @register decorator provides a cleaner way to register functions:

from iterativerecursion import IterativeRecursionEngine, FunctionReturn

engine = IterativeRecursionEngine()

@engine.register
def greet(name: str) -> FunctionReturn:
    print(f"Hello, {name}!")
    return FunctionReturn(
        returned_values={"next_name": "World"},
        next_function_to_call="farewell",
        arg_env_mapping={"name": "next_name"}
    )

@engine.register
def farewell(name: str) -> FunctionReturn:
    print(f"Goodbye, {name}!")
    return FunctionReturn(
        returned_values={}
    )

# Start execution and get final state
result = engine.start_function_caller(
    next_function_to_call="greet",
    environment_variables={"initial_name": "Alice"},
    arg_env_mapping={"name": "initial_name"}
)

Factorial Calculation

from iterativerecursion import IterativeRecursionEngine, FunctionReturn

def factorial_step(n: int, accumulator: int) -> FunctionReturn:
    if n <= 1:
        return FunctionReturn(
            returned_values={"result": accumulator}
        )
    # Auto-mapping: {"n": n-1, "accumulator": ...} automatically maps to itself
    return FunctionReturn(
        returned_values={
            "n": n - 1,
            "accumulator": accumulator * n
        },
        next_function_to_call="factorial_step"
    )

engine = IterativeRecursionEngine()
engine.add_function(factorial_step)
engine.start_function_caller(
    next_function_to_call="factorial_step",
    environment_variables={"n": 5, "accumulator": 1},
    arg_env_mapping={"n": "n", "accumulator": "accumulator"}
)

print(f"5! = {engine.environment_variables['result']}")  # Output: 5! = 120

Fibonacci Sequence (tail-recursive / iterative form)

from iterativerecursion import IterativeRecursionEngine, FunctionReturn

def fibonacci(n: int, a: int, b: int) -> FunctionReturn:
    if n == 0:
        return FunctionReturn(
            returned_values={"result": a}
        )
    # Auto-mapping handles {"n": "n", "a": "a", "b": "b"} automatically
    return FunctionReturn(
        returned_values={
            "n": n - 1,
            "a": b,
            "b": a + b
        },
        next_function_to_call="fibonacci"
    )

engine = IterativeRecursionEngine()
engine.add_function(fibonacci)
engine.start_function_caller(
    next_function_to_call="fibonacci",
    environment_variables={"n": 10, "a": 0, "b": 1},
    arg_env_mapping={"n": "n", "a": "a", "b": "b"}
)

print(f"Fibonacci(10) = {engine.environment_variables['result']}")  # Output: 55

Preventing Infinite Loops

Use the max_iterations parameter to prevent runaway execution:

from iterativerecursion import IterativeRecursionEngine, FunctionReturn

def infinite_loop(counter: int) -> FunctionReturn:
    print(f"Iteration: {counter}")
    # Auto-mapping: no need for {"counter": "counter"}
    return FunctionReturn(
        returned_values={"counter": counter + 1},
        next_function_to_call="infinite_loop"
    )

engine = IterativeRecursionEngine()
engine.add_function(infinite_loop)

try:
    engine.start_function_caller(
        next_function_to_call="infinite_loop",
        environment_variables={"counter": 0},
        arg_env_mapping={"counter": "counter"},
        max_iterations=10  # Safety limit
    )
except RuntimeError as e:
    print(f"Caught: {e}")
    # Output: Caught: Maximum iteration limit (10) reached...

API Reference

IterativeRecursionEngine

The main execution engine for iterative recursion.

Methods

__init__()

Creates a new engine instance.

engine = IterativeRecursionEngine()
add_function(function)

Registers a function with the engine.

  • Parameters: function - A callable that returns FunctionReturn
  • Returns: None
engine.add_function(my_function)
register(function)

Decorator to register a function with the engine. Alternative to add_function().

  • Parameters: function - A callable that returns FunctionReturn
  • Returns: The same function (for chaining)
@engine.register
def my_function(x: int) -> FunctionReturn:
    return FunctionReturn(
        returned_values={"result": x * 2}
    )
add_environment_variables(variables: dict[str, Any])

Adds or updates variables in the shared environment.

  • Parameters: variables - Dictionary of variable names and values
  • Returns: None
engine.add_environment_variables({"x": 10, "y": 20})
start_function_caller(next_function_to_call, environment_variables, arg_env_mapping, max_iterations=None)

Begins executing functions starting from the specified function.

  • Parameters:
    • next_function_to_call (str | None): Name of the first function to call. Pass None to terminate immediately after recording environment_variables.
    • environment_variables (dict[str, Any]): Initial environment variables
    • arg_env_mapping (dict[str, str]): Parameter mapping for first function
    • max_iterations (int | None): Maximum iterations allowed (default: None/unlimited)
  • Returns: dict[str, Any] - Final state of environment variables after execution
  • Raises:
    • KeyError: If function not found or environment variable missing
    • RuntimeError: If max_iterations limit is reached
    • ValueError: If function returns invalid structure
    • TypeError: If function return has wrong types
result = engine.start_function_caller(
    next_function_to_call="start_func",
    environment_variables={"value": 42},
    arg_env_mapping={"param": "value"},
    max_iterations=1000
)
# Access final state directly from result
print(result["some_value"])

Attributes

  • functions_dict (dict): Registry of available functions
  • environment_variables (dict): Shared state accessible to all functions

Type Definitions

FunctionReturn

Dataclass defining the required return structure for functions.

@dataclass
class FunctionReturn:
    returned_values: dict[str, Any]
    next_function_to_call: str | None = None
    arg_env_mapping: dict[str, str] = field(default_factory=dict)

Auto-mapping feature: If arg_env_mapping is not provided, it automatically maps each key in returned_values to itself. This means you rarely need to specify arg_env_mapping explicitly.

VarsDict

Type alias for variable dictionaries.

VarsDict = dict[str, Any]

Development

Running Tests

# Install with dev dependencies
pip install -e ".[dev]"

# Run tests
pytest tests/ -v

Test Coverage

The test suite includes:

  • Basic function chaining
  • Environment variable management
  • Error handling and validation
  • Runtime validation of return structures
  • Iteration limits
  • Decorator API (@register)
  • Return value access
  • Improved error messages
  • Complex scenarios (factorial, state machines)

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Author

Carlos A. Planchón - GitHub

Acknowledgments

This module was created as both a practical solution for deep recursion scenarios and an exploration of alternative execution patterns in Python.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

iterativerecursion-1.1.3.tar.gz (11.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

iterativerecursion-1.1.3-py3-none-any.whl (8.7 kB view details)

Uploaded Python 3

File details

Details for the file iterativerecursion-1.1.3.tar.gz.

File metadata

  • Download URL: iterativerecursion-1.1.3.tar.gz
  • Upload date:
  • Size: 11.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for iterativerecursion-1.1.3.tar.gz
Algorithm Hash digest
SHA256 59977fb213ac1bc937a7cf5cb6831858132c4fa2aa3df79e518302960ca5129f
MD5 dd12806b34bd4a2e1f9576a5737ae9f8
BLAKE2b-256 6a0759af12afb52a9dfd6f22d0e6f57264a9039b5ae12a131eb886e90784f30b

See more details on using hashes here.

Provenance

The following attestation bundles were made for iterativerecursion-1.1.3.tar.gz:

Publisher: publish.yml on carlosplanchon/iterativerecursion

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file iterativerecursion-1.1.3-py3-none-any.whl.

File metadata

File hashes

Hashes for iterativerecursion-1.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 7bf8b8fb2b73367c2ce37149905f853d12dc96351899b3fc7ceb625c00a0dca2
MD5 4f2d65872d099365188ceb1eefde1647
BLAKE2b-256 75618589f24da7a129182c0a2bdc590ea96dbc29099e31184dbae30b52c678c9

See more details on using hashes here.

Provenance

The following attestation bundles were made for iterativerecursion-1.1.3-py3-none-any.whl:

Publisher: publish.yml on carlosplanchon/iterativerecursion

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page