展示 HN:Tacopy – Python 的尾调用优化
Show HN: Tacopy – Tail Call Optimization for Python

原始链接: https://github.com/raaidrt/tacopy

## Tacopy:Python 的尾调用优化 Tacopy 是一个 Python 库,用于优化尾递归函数,防止堆栈溢出错误并提高性能。它通过使用抽象语法树 (AST) 操作将尾递归函数转换为迭代循环来实现。 **主要特性:** * **自动优化:** `@tacopy` 装饰器自动转换符合条件的函数。 * **防止堆栈溢出:** 处理任意深度的递归,无需 Python 的递归限制。 * **性能提升:** 基准测试显示,与标准递归相比,速度提高了 1.41 倍到 2.88 倍。 * **验证:** 在转换之前确保函数正确地是尾递归的,否则会引发错误。 * **元数据保留:** 维护文档字符串、类型提示和其他函数元数据。 **使用要求:** 函数必须在模块级别定义,直接自递归(没有互递归),并且不能是异步的。Tacopy 需要 Python 3.10 或更高版本。对于无效函数,会引发 `TailRecursionError`。用户可以使用 `show_transformed_code()` 预览转换后的代码。 Tacopy 通过 `pip install tacopy-optimization` 提供,并在 GitHub 上提供全面的测试套件和设计文档。

Hacker News 新闻 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 Show HN: Tacopy – Python 尾调用优化 (github.com/raaidrt) 4 点赞 raaid-rt 1 小时前 | 隐藏 | 过去 | 收藏 | 讨论 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

Tail-Call Optimization for Python

Tacopy is a Python library that provides a decorator to optimize tail-recursive functions by transforming them into iterative loops. This eliminates the risk of stack overflow errors for deep recursion.

  • Automatic Tail-Call Optimization: Transforms tail-recursive functions into efficient loops
  • Stack Overflow Prevention: Handle arbitrarily deep recursion without hitting Python's recursion limit
  • Significant Performance Gains: 1.41x-2.88x faster than regular recursion (see benchmarks)
  • Validation: Ensures functions are properly tail-recursive before transformation
  • No Runtime Overhead: Optimization happens once at decoration time
  • Preservation of Function Metadata: Keeps docstrings, type hints, and other metadata intact
# Using uv (recommended for development)
uv add tacopy-optimization

# Using pip
pip install tacopy-optimization
from tacopy import tacopy

@tacopy
def factorial_mod_k(acc: int, n: int, k: int) -> int:
    """Calculate (acc * n!) mod k using tail recursion."""
    if n == 0:
        return acc % k
    return factorial_mod_k(acc * n % k, n - 1, k)

# This would normally cause a stack overflow, but works with @tacopy
result = factorial_mod_k(1, 1_000_000, 79)
print(result)  # Output: 0

Tacopy uses AST (Abstract Syntax Tree) transformation to convert tail-recursive functions into iterative loops. For example:

Original function:

@tacopy
def factorial(n: int, acc: int = 1) -> int:
    if n == 0:
        return acc
    return factorial(n - 1, acc * n)

Transformed to (conceptually):

def factorial(n: int, acc: int = 1) -> int:
    _n = n
    _acc = acc
    while True:
        if _n == 0:
            return _acc
        _n, _acc = _n - 1, _acc * _n

The transformation:

  1. Hoists parameters to uniquely-named local variables (using UUIDs to avoid collisions)
  2. Wraps the function body in a while True loop
  3. Replaces tail-recursive calls with variable assignments and continue statements
@tacopy
def fibonacci(n: int, a: int = 0, b: int = 1) -> int:
    """Calculate the nth Fibonacci number."""
    if n == 0:
        return a
    if n == 1:
        return b
    return fibonacci(n - 1, b, a + b)

# Calculate very large Fibonacci numbers
fib_5000 = fibonacci(5000)

Greatest Common Divisor (GCD)

@tacopy
def gcd(a: int, b: int) -> int:
    """Calculate GCD using Euclidean algorithm."""
    if b == 0:
        return a
    return gcd(b, a % b)

print(gcd(1071, 462))  # Output: 21
@tacopy
def sum_to_n(n: int, acc: int = 0) -> int:
    """Calculate sum from 1 to n."""
    if n == 0:
        return acc
    return sum_to_n(n - 1, acc + n)

print(sum_to_n(100))  # Output: 5050

Requirements for Tail Recursion

For a function to be optimizable with @tacopy, it must be properly tail-recursive:

  1. Module-level function - The function must be defined at module level, not nested inside another function
  2. All recursive calls must be in tail position - the return value of the recursive call must be immediately returned, with no further operations
  3. No async functions - Async functions are not supported due to potential issues with shared state
@tacopy
def valid(n, acc):
    if n == 0:
        return acc
    return valid(n - 1, acc + n)  # � Tail call - immediately returned

Invalid (Non-Tail) Recursion

@tacopy
def invalid(n):
    if n == 0:
        return 1
    return n * invalid(n - 1)  # � NOT tail recursive - multiplication after call

Nested Functions Not Allowed

def outer():
    @tacopy  # ❌ Error: nested functions cannot use @tacopy
    def helper(n, acc):
        if n == 0:
            return acc
        return helper(n - 1, acc + n)
    return helper(10, 0)

# ✅ Correct: Extract to module level
@tacopy
def helper(n, acc):
    if n == 0:
        return acc
    return helper(n - 1, acc + n)

def outer():
    return helper(10, 0)

The decorator will raise a TailRecursionError if the function is not properly tail-recursive or if it is nested inside another function.

You can view the transformed code without executing it:

from tacopy import show_transformed_code

def factorial(n: int, acc: int = 1) -> int:
    if n == 0:
        return acc
    return factorial(n - 1, acc * n)

print(show_transformed_code(factorial))

Tacopy provides significant performance improvements over regular recursion. Below are benchmark results comparing tail-recursive functions with and without the @tacopy decorator (100 runs each, recursion depth of 1000):

Function Without tacopy With tacopy Speedup Performance Change
factorial(1000) 0.000230 ± 0.000117s 0.000163 ± 0.000019s 1.41x faster 29.2% faster
fibonacci(1000) 0.000083 ± 0.000008s 0.000045 ± 0.000013s 1.86x faster 46.4% faster
sum_to_n(1000) 0.000074 ± 0.000013s 0.000026 ± 0.000002s 2.88x faster 65.2% faster
power(2, 1000) 0.000087 ± 0.000008s 0.000044 ± 0.000008s 1.97x faster 49.3% faster
  • 1.41x-2.88x speedup for typical tail-recursive functions
  • Eliminates stack overflow: Regular Python recursion is limited to 1000 calls, while tacopy can handle millions
  • Lower variance: Tacopy-optimized functions show more consistent performance (lower standard deviation)

You can run the benchmarking suite yourself:

uv run python benchmarking/benchmark.py

The benchmarks use a recursion depth of 1000 for non-tacopy functions and pure integer arithmetic in the sample implementations. See benchmarking/README.md for more details.

# Clone the repository
git clone https://github.com/yourusername/tacopy.git
cd tacopy

# Install dependencies using uv
uv sync

# Activate virtual environment
source .venv/bin/activate  # On Unix/macOS
# or
.venv\Scripts\activate  # On Windows
# Run all tests
pytest tests/ -v

# Run with coverage
pytest tests/ --cov=tacopy --cov-report=html

# Update snapshots (if you've changed transformation logic)
pytest tests/ --snapshot-update
tacopy/
├── src/
│   └── tacopy/
│       ├── __init__.py      # Main decorator and public API
│       ├── validator.py     # Tail recursion validation
│       ├── transformer.py   # AST transformation logic
│       └── unparser.py      # AST to code conversion
├── tests/
│   ├── test_validator.py    # Validator unit tests
│   ├── test_transformer.py  # Transformer unit tests
│   └── test_integration.py  # End-to-end integration tests
├── main.py                  # Example usage
├── DESIGN.md                # Design document
└── README.md                # This file
  1. Module-level functions only: The decorator can only be applied to functions defined at module level, not nested inside other functions. If you need to optimize a helper function, extract it to module level.
  2. Async functions not supported: The decorator will raise an error if applied to async functions
  3. Source code required: The function's source code must be accessible via inspect.getsource()
  4. No mutual recursion: Only direct self-recursion is optimized
  5. Python 3.10+: Requires Python 3.10 or higher

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

This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.

联系我们 contact @ memedata.com