iterativerecursion 1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iterativerecursion/__init__.py +5 -0
- iterativerecursion/iterativerecursion.py +223 -0
- iterativerecursion-1.0.dist-info/METADATA +399 -0
- iterativerecursion-1.0.dist-info/RECORD +7 -0
- iterativerecursion-1.0.dist-info/WHEEL +5 -0
- iterativerecursion-1.0.dist-info/licenses/LICENSE +21 -0
- iterativerecursion-1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Callable
|
|
5
|
+
|
|
6
|
+
VarsDict = dict[str, Any]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class FunctionReturn:
|
|
11
|
+
"""
|
|
12
|
+
Return type for functions executed by IterativeRecursionEngine.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
returned_values: Dictionary of values to update in the environment.
|
|
16
|
+
next_function_to_call: Name of the next function to execute, or None to
|
|
17
|
+
terminate execution.
|
|
18
|
+
arg_env_mapping: Mapping of parameter names to environment variable keys
|
|
19
|
+
for the next function call. If not provided, automatically maps
|
|
20
|
+
returned_values keys to themselves (e.g., {"x": 5} maps to {"x": "x"}).
|
|
21
|
+
|
|
22
|
+
Example:
|
|
23
|
+
# Simplest form - auto-mapping
|
|
24
|
+
return FunctionReturn(
|
|
25
|
+
returned_values={"counter": counter + 1},
|
|
26
|
+
next_function_to_call="infinite_loop"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Custom mapping when needed
|
|
30
|
+
return FunctionReturn(
|
|
31
|
+
returned_values={"result": 42},
|
|
32
|
+
next_function_to_call="process",
|
|
33
|
+
arg_env_mapping={"input": "result"} # Maps param 'input' to env var 'result'
|
|
34
|
+
)
|
|
35
|
+
"""
|
|
36
|
+
returned_values: dict[str, Any]
|
|
37
|
+
next_function_to_call: str | None = None
|
|
38
|
+
arg_env_mapping: dict[str, str] = field(default_factory=dict)
|
|
39
|
+
|
|
40
|
+
def __post_init__(self):
|
|
41
|
+
"""Auto-populate arg_env_mapping if not provided."""
|
|
42
|
+
if not self.arg_env_mapping:
|
|
43
|
+
# Map each returned value key to itself
|
|
44
|
+
self.arg_env_mapping = {k: k for k in self.returned_values.keys()}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class IterativeRecursionEngine:
|
|
48
|
+
"""
|
|
49
|
+
Execute functions and "call" between them without recursion.
|
|
50
|
+
|
|
51
|
+
You have a dict of variables on self.environment_variables that work as
|
|
52
|
+
"global variables" inside of the executor.
|
|
53
|
+
|
|
54
|
+
When you call a function, the values that return will update
|
|
55
|
+
self.environment_variables, you pass only self.environment_variables
|
|
56
|
+
as arguments to a function.
|
|
57
|
+
"""
|
|
58
|
+
def __init__(self):
|
|
59
|
+
self.functions_dict: dict[str, Callable[..., FunctionReturn]] = {}
|
|
60
|
+
self.environment_variables: VarsDict = {}
|
|
61
|
+
|
|
62
|
+
def _validate_function_return(self, resp: Any, func_name: str) -> None:
|
|
63
|
+
"""
|
|
64
|
+
Validate function return is a FunctionReturn instance.
|
|
65
|
+
|
|
66
|
+
:param resp: The return value from a function
|
|
67
|
+
:param func_name: Name of the function that returned the value
|
|
68
|
+
:raises TypeError: If return value is not a FunctionReturn instance
|
|
69
|
+
"""
|
|
70
|
+
if not isinstance(resp, FunctionReturn):
|
|
71
|
+
raise TypeError(
|
|
72
|
+
f"Function '{func_name}' must return a FunctionReturn instance, "
|
|
73
|
+
f"got {type(resp).__name__}. "
|
|
74
|
+
f"Example: return FunctionReturn(returned_values={{'x': 5}}, next_function_to_call='next_func')"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Validate types (dataclass doesn't enforce at runtime)
|
|
78
|
+
if not isinstance(resp.arg_env_mapping, dict):
|
|
79
|
+
raise TypeError(
|
|
80
|
+
f"Function '{func_name}': arg_env_mapping must be dict, "
|
|
81
|
+
f"got {type(resp.arg_env_mapping).__name__}"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
if resp.next_function_to_call is not None and not isinstance(
|
|
85
|
+
resp.next_function_to_call, str
|
|
86
|
+
):
|
|
87
|
+
raise TypeError(
|
|
88
|
+
f"Function '{func_name}': next_function_to_call must be str or None, "
|
|
89
|
+
f"got {type(resp.next_function_to_call).__name__}"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if not isinstance(resp.returned_values, dict):
|
|
93
|
+
raise TypeError(
|
|
94
|
+
f"Function '{func_name}': returned_values must be dict, "
|
|
95
|
+
f"got {type(resp.returned_values).__name__}"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def _resolve_arguments(
|
|
99
|
+
self, arg_env_mapping: dict[str, str], func_name: str
|
|
100
|
+
) -> dict[str, Any]:
|
|
101
|
+
"""
|
|
102
|
+
Resolve argument names to actual values from environment.
|
|
103
|
+
|
|
104
|
+
:param arg_env_mapping: Mapping of parameter names to environment variable keys
|
|
105
|
+
:param func_name: Name of the function (for error messages)
|
|
106
|
+
:return: Dictionary mapping parameter names to actual values
|
|
107
|
+
:raises KeyError: If required environment variables are missing
|
|
108
|
+
"""
|
|
109
|
+
missing_vars = set(arg_env_mapping.values()) - self.environment_variables.keys()
|
|
110
|
+
if missing_vars:
|
|
111
|
+
available = set(self.environment_variables.keys())
|
|
112
|
+
raise KeyError(
|
|
113
|
+
f"Function '{func_name}' requires environment variables "
|
|
114
|
+
f"that don't exist: {missing_vars}. "
|
|
115
|
+
f"Available variables: {available if available else '(none)'}"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
arg: self.environment_variables[env_key]
|
|
120
|
+
for arg, env_key in arg_env_mapping.items()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
def start_function_caller(
|
|
124
|
+
self,
|
|
125
|
+
next_function_to_call: str,
|
|
126
|
+
environment_variables: VarsDict,
|
|
127
|
+
arg_env_mapping: VarsDict,
|
|
128
|
+
max_iterations: int | None = None
|
|
129
|
+
) -> VarsDict:
|
|
130
|
+
"""
|
|
131
|
+
Start the execution of a function.
|
|
132
|
+
|
|
133
|
+
:param next_function_to_call: What function to call first when starting
|
|
134
|
+
this function. If next_function_to_call is None, this function stops
|
|
135
|
+
and returns.
|
|
136
|
+
:param environment_variables: Variables to add to the environment.
|
|
137
|
+
:param arg_env_mapping: Arguments to call on the first function.
|
|
138
|
+
:param max_iterations: Maximum number of function calls allowed before
|
|
139
|
+
raising an error. None means unlimited iterations. Defaults to None.
|
|
140
|
+
:return: The final state of environment_variables after execution completes
|
|
141
|
+
:raises RuntimeError: If max_iterations limit is reached
|
|
142
|
+
:raises KeyError: If function not found or environment variable missing
|
|
143
|
+
:raises ValueError: If function returns invalid structure
|
|
144
|
+
:raises TypeError: If function return has wrong types
|
|
145
|
+
"""
|
|
146
|
+
if next_function_to_call is None:
|
|
147
|
+
return self.environment_variables
|
|
148
|
+
|
|
149
|
+
self.environment_variables.update(environment_variables)
|
|
150
|
+
|
|
151
|
+
# Resolve initial arguments
|
|
152
|
+
arg_env_mapping = self._resolve_arguments(arg_env_mapping, next_function_to_call)
|
|
153
|
+
|
|
154
|
+
iteration_count = 0
|
|
155
|
+
while True:
|
|
156
|
+
# Check iteration limit
|
|
157
|
+
if max_iterations is not None:
|
|
158
|
+
if iteration_count >= max_iterations:
|
|
159
|
+
raise RuntimeError(
|
|
160
|
+
f"Maximum iteration limit ({max_iterations}) reached. "
|
|
161
|
+
f"This may indicate an infinite loop. "
|
|
162
|
+
f"Last function called: {next_function_to_call}"
|
|
163
|
+
)
|
|
164
|
+
iteration_count += 1
|
|
165
|
+
|
|
166
|
+
# Execute function
|
|
167
|
+
resp = self.functions_dict[next_function_to_call](**arg_env_mapping)
|
|
168
|
+
|
|
169
|
+
# Validate return structure
|
|
170
|
+
self._validate_function_return(resp, next_function_to_call)
|
|
171
|
+
|
|
172
|
+
# Update environment with returned values
|
|
173
|
+
self.environment_variables.update(resp.returned_values)
|
|
174
|
+
|
|
175
|
+
# Determine next function to call
|
|
176
|
+
next_function_to_call = resp.next_function_to_call
|
|
177
|
+
if not next_function_to_call:
|
|
178
|
+
return self.environment_variables
|
|
179
|
+
elif next_function_to_call not in self.functions_dict:
|
|
180
|
+
available_funcs = set(self.functions_dict.keys())
|
|
181
|
+
raise KeyError(
|
|
182
|
+
f"Function '{next_function_to_call}' not found in registry. "
|
|
183
|
+
f"Available functions: {available_funcs if available_funcs else '(none)'}"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Resolve arguments for next function call
|
|
187
|
+
arg_env_mapping = self._resolve_arguments(
|
|
188
|
+
resp.arg_env_mapping, next_function_to_call
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def add_environment_variables(self, environment_variables_dict_update: VarsDict):
|
|
192
|
+
"""
|
|
193
|
+
Define new variables inside of the executor.
|
|
194
|
+
|
|
195
|
+
:param environment_variables_dict_update: Dict of new variables to add.
|
|
196
|
+
"""
|
|
197
|
+
self.environment_variables.update(environment_variables_dict_update)
|
|
198
|
+
|
|
199
|
+
def add_function(self, function: Callable[..., FunctionReturn]) -> None:
|
|
200
|
+
"""
|
|
201
|
+
Define new functions inside of the executor.
|
|
202
|
+
|
|
203
|
+
:param function: Function to add. Must return FunctionReturn structure.
|
|
204
|
+
"""
|
|
205
|
+
self.functions_dict[function.__name__] = function
|
|
206
|
+
|
|
207
|
+
def register(self, func: Callable[..., FunctionReturn]) -> Callable[..., FunctionReturn]:
|
|
208
|
+
"""
|
|
209
|
+
Decorator to register a function with the engine.
|
|
210
|
+
|
|
211
|
+
Example:
|
|
212
|
+
@engine.register
|
|
213
|
+
def my_func(x: int) -> FunctionReturn:
|
|
214
|
+
return FunctionReturn(
|
|
215
|
+
returned_values={"result": x * 2},
|
|
216
|
+
next_function_to_call=None
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
:param func: Function to register
|
|
220
|
+
:return: The same function (for chaining)
|
|
221
|
+
"""
|
|
222
|
+
self.add_function(func)
|
|
223
|
+
return func
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: iterativerecursion
|
|
3
|
+
Version: 1.0
|
|
4
|
+
Summary: A Python module to simulate recursive function calls using iteration, providing explicit control over execution flow and avoiding stack overflow issues.
|
|
5
|
+
Author-email: "Carlos A. Planchón" <carlosandresplanchonprestes@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/carlosplanchon/iterativerecursion
|
|
8
|
+
Keywords: iterative,recursion
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# iterativerecursion
|
|
25
|
+
|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
*A Python module to simulate recursive function calls using iteration, providing explicit control over execution flow and avoiding stack overflow issues.*
|
|
29
|
+
|
|
30
|
+
## Overview
|
|
31
|
+
|
|
32
|
+
`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.
|
|
33
|
+
|
|
34
|
+
### Why Use This?
|
|
35
|
+
|
|
36
|
+
- **Avoid Stack Overflow**: Handle deep recursion without hitting Python's recursion limit
|
|
37
|
+
- **Explicit Control**: See and control the exact flow of function calls
|
|
38
|
+
- **Debugging**: Easier to trace execution without deep call stacks
|
|
39
|
+
- **State Management**: Shared environment for passing data between functions
|
|
40
|
+
- **Safety**: Built-in iteration limits to prevent infinite loops
|
|
41
|
+
|
|
42
|
+
### When to Use This
|
|
43
|
+
|
|
44
|
+
This library is useful when you need:
|
|
45
|
+
- Deep recursion that exceeds Python's stack limit (~1000 calls)
|
|
46
|
+
- Explicit control over recursive execution flow
|
|
47
|
+
- To convert recursive algorithms to iterative ones systematically
|
|
48
|
+
- State machines or complex control flow patterns
|
|
49
|
+
|
|
50
|
+
## Scope & limitations
|
|
51
|
+
|
|
52
|
+
This engine is essentially a **trampoline / dispatcher**: each step returns the name of the next function to run.
|
|
53
|
+
That makes it a great fit for **tail recursion (linear step-by-step recursion)**, workflows, and state machines.
|
|
54
|
+
|
|
55
|
+
It does **not** automatically emulate a full call stack. Patterns that require “returning to the caller”
|
|
56
|
+
(e.g. tree recursion like `fib(n-1) + fib(n-2)`, post-order DFS reductions) require you to model an explicit
|
|
57
|
+
stack/frames (continuations) inside `environment_variables`.
|
|
58
|
+
|
|
59
|
+
For most cases, **normal recursion is simpler and preferred**. Use this when recursion depth or explicit control becomes a concern.
|
|
60
|
+
|
|
61
|
+
## Documentation
|
|
62
|
+
|
|
63
|
+
📚 **[View interactive documentation on DeepWiki](https://deepwiki.com/carlosplanchon/iterativerecursion)**
|
|
64
|
+
|
|
65
|
+
Explore the full API reference, examples, and guides in an interactive format.
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
69
|
+
### Using uv
|
|
70
|
+
```bash
|
|
71
|
+
uv add iterativerecursion
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Using pip
|
|
75
|
+
```bash
|
|
76
|
+
pip install iterativerecursion
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Quick Start
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from iterativerecursion import IterativeRecursionEngine, FunctionReturn
|
|
83
|
+
|
|
84
|
+
def greet(name: str) -> FunctionReturn:
|
|
85
|
+
print(f"Hello, {name}!")
|
|
86
|
+
return FunctionReturn(
|
|
87
|
+
returned_values={"next_name": "World"},
|
|
88
|
+
next_function_to_call="farewell",
|
|
89
|
+
arg_env_mapping={"name": "next_name"}
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def farewell(name: str) -> FunctionReturn:
|
|
93
|
+
print(f"Goodbye, {name}!")
|
|
94
|
+
return FunctionReturn(
|
|
95
|
+
returned_values={}
|
|
96
|
+
# next_function_to_call defaults to None to terminate
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Create engine and register functions
|
|
100
|
+
engine = IterativeRecursionEngine()
|
|
101
|
+
engine.add_function(greet)
|
|
102
|
+
engine.add_function(farewell)
|
|
103
|
+
|
|
104
|
+
# Start execution
|
|
105
|
+
engine.start_function_caller(
|
|
106
|
+
next_function_to_call="greet",
|
|
107
|
+
environment_variables={"initial_name": "Alice"},
|
|
108
|
+
arg_env_mapping={"name": "initial_name"}
|
|
109
|
+
)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Output:**
|
|
113
|
+
```
|
|
114
|
+
Hello, Alice!
|
|
115
|
+
Goodbye, World!
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## How It Works
|
|
119
|
+
|
|
120
|
+
Functions return a `FunctionReturn` dataclass instance with three attributes:
|
|
121
|
+
|
|
122
|
+
| Attribute | Type | Description |
|
|
123
|
+
|-----------|------|-------------|
|
|
124
|
+
| `returned_values` | `dict[str, Any]` | Values to store in the shared environment |
|
|
125
|
+
| `next_function_to_call` | `str \| None` | Name of the next function to execute, or `None` to stop (default: `None`) |
|
|
126
|
+
| `arg_env_mapping` | `dict[str, str]` | Mapping of parameter names to environment variable keys (default: auto-mapped from `returned_values` keys) |
|
|
127
|
+
|
|
128
|
+
**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.
|
|
129
|
+
|
|
130
|
+
The engine maintains a shared environment where functions can store and retrieve values across calls.
|
|
131
|
+
|
|
132
|
+
## Examples
|
|
133
|
+
|
|
134
|
+
### Using the @register Decorator
|
|
135
|
+
|
|
136
|
+
The `@register` decorator provides a cleaner way to register functions:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from iterativerecursion import IterativeRecursionEngine, FunctionReturn
|
|
140
|
+
|
|
141
|
+
engine = IterativeRecursionEngine()
|
|
142
|
+
|
|
143
|
+
@engine.register
|
|
144
|
+
def greet(name: str) -> FunctionReturn:
|
|
145
|
+
print(f"Hello, {name}!")
|
|
146
|
+
return FunctionReturn(
|
|
147
|
+
returned_values={"next_name": "World"},
|
|
148
|
+
next_function_to_call="farewell",
|
|
149
|
+
arg_env_mapping={"name": "next_name"}
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
@engine.register
|
|
153
|
+
def farewell(name: str) -> FunctionReturn:
|
|
154
|
+
print(f"Goodbye, {name}!")
|
|
155
|
+
return FunctionReturn(
|
|
156
|
+
returned_values={}
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Start execution and get final state
|
|
160
|
+
result = engine.start_function_caller(
|
|
161
|
+
next_function_to_call="greet",
|
|
162
|
+
environment_variables={"initial_name": "Alice"},
|
|
163
|
+
arg_env_mapping={"name": "initial_name"}
|
|
164
|
+
)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Factorial Calculation
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from iterativerecursion import IterativeRecursionEngine, FunctionReturn
|
|
171
|
+
|
|
172
|
+
def factorial_step(n: int, accumulator: int) -> FunctionReturn:
|
|
173
|
+
if n <= 1:
|
|
174
|
+
return FunctionReturn(
|
|
175
|
+
returned_values={"result": accumulator}
|
|
176
|
+
)
|
|
177
|
+
# Auto-mapping: {"n": n-1, "accumulator": ...} automatically maps to itself
|
|
178
|
+
return FunctionReturn(
|
|
179
|
+
returned_values={
|
|
180
|
+
"n": n - 1,
|
|
181
|
+
"accumulator": accumulator * n
|
|
182
|
+
},
|
|
183
|
+
next_function_to_call="factorial_step"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
engine = IterativeRecursionEngine()
|
|
187
|
+
engine.add_function(factorial_step)
|
|
188
|
+
engine.start_function_caller(
|
|
189
|
+
next_function_to_call="factorial_step",
|
|
190
|
+
environment_variables={"n": 5, "accumulator": 1},
|
|
191
|
+
arg_env_mapping={"n": "n", "accumulator": "accumulator"}
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
print(f"5! = {engine.environment_variables['result']}") # Output: 5! = 120
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Fibonacci Sequence (tail-recursive / iterative form)
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
from iterativerecursion import IterativeRecursionEngine, FunctionReturn
|
|
201
|
+
|
|
202
|
+
def fibonacci(n: int, a: int, b: int) -> FunctionReturn:
|
|
203
|
+
if n == 0:
|
|
204
|
+
return FunctionReturn(
|
|
205
|
+
returned_values={"result": a}
|
|
206
|
+
)
|
|
207
|
+
# Auto-mapping handles {"n": "n", "a": "a", "b": "b"} automatically
|
|
208
|
+
return FunctionReturn(
|
|
209
|
+
returned_values={
|
|
210
|
+
"n": n - 1,
|
|
211
|
+
"a": b,
|
|
212
|
+
"b": a + b
|
|
213
|
+
},
|
|
214
|
+
next_function_to_call="fibonacci"
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
engine = IterativeRecursionEngine()
|
|
218
|
+
engine.add_function(fibonacci)
|
|
219
|
+
engine.start_function_caller(
|
|
220
|
+
next_function_to_call="fibonacci",
|
|
221
|
+
environment_variables={"n": 10, "a": 0, "b": 1},
|
|
222
|
+
arg_env_mapping={"n": "n", "a": "a", "b": "b"}
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
print(f"Fibonacci(10) = {engine.environment_variables['result']}") # Output: 55
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Preventing Infinite Loops
|
|
229
|
+
|
|
230
|
+
Use the `max_iterations` parameter to prevent runaway execution:
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
from iterativerecursion import IterativeRecursionEngine, FunctionReturn
|
|
234
|
+
|
|
235
|
+
def infinite_loop(counter: int) -> FunctionReturn:
|
|
236
|
+
print(f"Iteration: {counter}")
|
|
237
|
+
# Auto-mapping: no need for {"counter": "counter"}
|
|
238
|
+
return FunctionReturn(
|
|
239
|
+
returned_values={"counter": counter + 1},
|
|
240
|
+
next_function_to_call="infinite_loop"
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
engine = IterativeRecursionEngine()
|
|
244
|
+
engine.add_function(infinite_loop)
|
|
245
|
+
|
|
246
|
+
try:
|
|
247
|
+
engine.start_function_caller(
|
|
248
|
+
next_function_to_call="infinite_loop",
|
|
249
|
+
environment_variables={"counter": 0},
|
|
250
|
+
arg_env_mapping={"counter": "counter"},
|
|
251
|
+
max_iterations=10 # Safety limit
|
|
252
|
+
)
|
|
253
|
+
except RuntimeError as e:
|
|
254
|
+
print(f"Caught: {e}")
|
|
255
|
+
# Output: Caught: Maximum iteration limit (10) reached...
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## API Reference
|
|
259
|
+
|
|
260
|
+
### `IterativeRecursionEngine`
|
|
261
|
+
|
|
262
|
+
The main execution engine for iterative recursion.
|
|
263
|
+
|
|
264
|
+
#### Methods
|
|
265
|
+
|
|
266
|
+
##### `__init__()`
|
|
267
|
+
Creates a new engine instance.
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
engine = IterativeRecursionEngine()
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
##### `add_function(function)`
|
|
274
|
+
Registers a function with the engine.
|
|
275
|
+
|
|
276
|
+
- **Parameters**: `function` - A callable that returns `FunctionReturn`
|
|
277
|
+
- **Returns**: None
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
engine.add_function(my_function)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
##### `register(function)`
|
|
284
|
+
Decorator to register a function with the engine. Alternative to `add_function()`.
|
|
285
|
+
|
|
286
|
+
- **Parameters**: `function` - A callable that returns `FunctionReturn`
|
|
287
|
+
- **Returns**: The same function (for chaining)
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
@engine.register
|
|
291
|
+
def my_function(x: int) -> FunctionReturn:
|
|
292
|
+
return FunctionReturn(
|
|
293
|
+
returned_values={"result": x * 2}
|
|
294
|
+
)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
##### `add_environment_variables(variables: dict[str, Any])`
|
|
298
|
+
Adds or updates variables in the shared environment.
|
|
299
|
+
|
|
300
|
+
- **Parameters**: `variables` - Dictionary of variable names and values
|
|
301
|
+
- **Returns**: None
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
engine.add_environment_variables({"x": 10, "y": 20})
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
##### `start_function_caller(next_function_to_call, environment_variables, arg_env_mapping, max_iterations=None)`
|
|
308
|
+
Begins executing functions starting from the specified function.
|
|
309
|
+
|
|
310
|
+
- **Parameters**:
|
|
311
|
+
- `next_function_to_call` (str): Name of the first function to call
|
|
312
|
+
- `environment_variables` (dict[str, Any]): Initial environment variables
|
|
313
|
+
- `arg_env_mapping` (dict[str, str]): Parameter mapping for first function
|
|
314
|
+
- `max_iterations` (int | None): Maximum iterations allowed (default: None/unlimited)
|
|
315
|
+
- **Returns**: `dict[str, Any]` - Final state of environment variables after execution
|
|
316
|
+
- **Raises**:
|
|
317
|
+
- `KeyError`: If function not found or environment variable missing
|
|
318
|
+
- `RuntimeError`: If `max_iterations` limit is reached
|
|
319
|
+
- `ValueError`: If function returns invalid structure
|
|
320
|
+
- `TypeError`: If function return has wrong types
|
|
321
|
+
|
|
322
|
+
```python
|
|
323
|
+
result = engine.start_function_caller(
|
|
324
|
+
next_function_to_call="start_func",
|
|
325
|
+
environment_variables={"value": 42},
|
|
326
|
+
arg_env_mapping={"param": "value"},
|
|
327
|
+
max_iterations=1000
|
|
328
|
+
)
|
|
329
|
+
# Access final state directly from result
|
|
330
|
+
print(result["some_value"])
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
#### Attributes
|
|
334
|
+
|
|
335
|
+
- `functions_dict` (dict): Registry of available functions
|
|
336
|
+
- `environment_variables` (dict): Shared state accessible to all functions
|
|
337
|
+
|
|
338
|
+
### Type Definitions
|
|
339
|
+
|
|
340
|
+
#### `FunctionReturn`
|
|
341
|
+
Dataclass defining the required return structure for functions.
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
@dataclass
|
|
345
|
+
class FunctionReturn:
|
|
346
|
+
returned_values: dict[str, Any]
|
|
347
|
+
next_function_to_call: str | None = None
|
|
348
|
+
arg_env_mapping: dict[str, str] = field(default_factory=dict)
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**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.
|
|
352
|
+
|
|
353
|
+
#### `VarsDict`
|
|
354
|
+
Type alias for variable dictionaries.
|
|
355
|
+
|
|
356
|
+
```python
|
|
357
|
+
VarsDict = dict[str, Any]
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Development
|
|
361
|
+
|
|
362
|
+
### Running Tests
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
# Install with dev dependencies
|
|
366
|
+
pip install -e ".[dev]"
|
|
367
|
+
|
|
368
|
+
# Run tests
|
|
369
|
+
pytest tests/ -v
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Test Coverage
|
|
373
|
+
|
|
374
|
+
The test suite includes:
|
|
375
|
+
- Basic function chaining
|
|
376
|
+
- Environment variable management
|
|
377
|
+
- Error handling and validation
|
|
378
|
+
- Runtime validation of return structures
|
|
379
|
+
- Iteration limits
|
|
380
|
+
- Decorator API (`@register`)
|
|
381
|
+
- Return value access
|
|
382
|
+
- Improved error messages
|
|
383
|
+
- Complex scenarios (factorial, state machines)
|
|
384
|
+
|
|
385
|
+
## Contributing
|
|
386
|
+
|
|
387
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
388
|
+
|
|
389
|
+
## License
|
|
390
|
+
|
|
391
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
392
|
+
|
|
393
|
+
## Author
|
|
394
|
+
|
|
395
|
+
Carlos A. Planchón - [GitHub](https://github.com/carlosplanchon/iterativerecursion)
|
|
396
|
+
|
|
397
|
+
## Acknowledgments
|
|
398
|
+
|
|
399
|
+
This module was created as both a practical solution for deep recursion scenarios and an exploration of alternative execution patterns in Python.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
iterativerecursion/__init__.py,sha256=wVsJOPN8Xoqx00tt01GU_7_sa2qPCEG2BDcNiCpuXr0,223
|
|
2
|
+
iterativerecursion/iterativerecursion.py,sha256=IisVgzbFlBPRCVIPSuTbOc_-JwpRvIyMHBWW8BtqCEI,8942
|
|
3
|
+
iterativerecursion-1.0.dist-info/licenses/LICENSE,sha256=dUqwRhq9nAYp-zfLbCuFB1d_dlg7PjxGZzNMPCTaa0c,1076
|
|
4
|
+
iterativerecursion-1.0.dist-info/METADATA,sha256=jDKPqoSevkN5B1zCRpLG-ziYyQq5ojwZFO4yztdSJI4,12400
|
|
5
|
+
iterativerecursion-1.0.dist-info/WHEEL,sha256=YLJXdYXQ2FQ0Uqn2J-6iEIC-3iOey8lH3xCtvFLkd8Q,91
|
|
6
|
+
iterativerecursion-1.0.dist-info/top_level.txt,sha256=L5ILEUqI0znr68qbm6FwMljLGneyLq9u2yKG80o0q10,19
|
|
7
|
+
iterativerecursion-1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 Carlos A. Planchón
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
iterativerecursion
|