Có thể đệ quy đuôi trong Python không?

Tôi đang xem qua ghi chú xuất sắc của Kyle Miller. “Đệ quy cuộc gọi đuôi trong Python” và quyết định thử nghiệm các biến thể của kỹ thuật

Ý tưởng là. người ta có thể muốn loại bỏ việc sử dụng ngăn xếp lệnh gọi ngôn ngữ

maximum recursion depth exceeded in comparison
3 trong trường hợp "gọi đuôi" (một lệnh gọi hàm mà kết quả không được sử dụng bởi hàm gọi mà thay vào đó được trả về ngay lập tức). Loại bỏ cuộc gọi đuôi có thể vừa tăng tốc chương trình vừa cắt giảm chi phí duy trì khung ngăn xếp trung gian và môi trường sẽ không bao giờ được sử dụng lại

Ghi chú chỉ ra một cách chính xác rằng

maximum recursion depth exceeded in comparison
3 cố tình không có câu lệnh
maximum recursion depth exceeded in comparison
5, một công cụ mà người ta có thể sử dụng để thực hiện loại bỏ cuộc gọi đuôi thực. Vì vậy, Kyle Miller đã xây dựng một sự thay thế dựa trên cấu trúc dữ liệu cho ngăn xếp cuộc gọi, cho phép một người làm việc xung quanh giới hạn ngăn xếp cho một chức năng cụ thể (không thay đổi bất kỳ cấu hình
maximum recursion depth exceeded in comparison
3 nào và không thay đổi hành vi của các chức năng khác)

Tất nhiên,

maximum recursion depth exceeded in comparison
3 có một số điều khiển luồng điều khiển kỳ lạ.
maximum recursion depth exceeded in comparison
1 và
maximum recursion depth exceeded in comparison
2. Vì vậy, tôi quyết định xây dựng một giải pháp dựa trên
maximum recursion depth exceeded in comparison
3 của riêng chúng tôi bằng cách sử dụng
maximum recursion depth exceeded in comparison
1

Vui lòng đọc tiếp để biết cách chúng tôi thực hiện việc này và một số ví dụ

Hãy xem một ví dụ về vấn đề. Lưu ý chức năng tự gọi (ngớ ngẩn) không thành công vì nó chạy hết ngăn xếp cuộc gọi trước khi hoàn thành phép tính của nó

Trong 1]

def recursive_example(n, d=1):
    if n <= 1:
        return d
    else:
        return recursive_example(n - 1, d + 1)

try:
    recursive_example(10000)
except Exception as ex:
    print(ex)

maximum recursion depth exceeded in comparison

Tất nhiên, nắm bắt gọn gàng đệ quy thừa (như

maximum recursion depth exceeded in comparison
3 đã làm ở trên) là một tính năng. Đó là một cách để ngăn chặn các lần truy cập chạy trốn có thể xảy ra

Tuy nhiên, nếu chúng ta muốn một chức năng cụ thể vượt quá giới hạn này (đặc biệt là đối với các cuộc gọi đuôi, không yêu cầu chi phí bộ nhớ. ). chúng ta cần thiết lập một khung tương tự như “Đệ quy cuộc gọi đuôi trong Python”

Trước tiên, chúng tôi xây dựng một "thunk" để biểu thị việc đánh giá một hàm với tất cả các đối số được chỉ định, nhưng điều đó vẫn chưa xảy ra. Chúng tôi triển khai các tính toán đang chờ xử lý với lớp

maximum recursion depth exceeded in comparison
6 (nguồn tại đây). Thêm một chút là. chúng tôi có
maximum recursion depth exceeded in comparison
7 mở rộng
maximum recursion depth exceeded in comparison
8, vì vậy chúng tôi có thể sử dụng
maximum recursion depth exceeded in comparison
1 để nhảy ra khỏi bối cảnh chức năng hiện tại của chúng tôi

Sau đó, khi chúng tôi có những gì thường là "cuộc gọi đuôi" có dạng "

maximum recursion depth exceeded in comparison
00", thay vào đó chúng tôi viết "
maximum recursion depth exceeded in comparison
01". Ý tưởng là. chúng tôi kết thúc chức năng hiện tại của mình bằng cách tăng ngoại lệ và chính ngoại lệ đó có hướng dẫn cho bước tiếp theo mong muốn hoặc tiếp tục tính toán. Sau đó, một trình bao bọc bên ngoài sẽ đánh giá lặp đi lặp lại bất kỳ
maximum recursion depth exceeded in comparison
7 nào gặp phải. Do đó, bất kỳ đệ quy đuôi nào cũng được thay thế bằng phép lặp và chúng tôi đã loại bỏ việc sử dụng ngăn xếp và bộ nhớ của các lệnh gọi đuôi. Cũng có thể sử dụng ký hiệu kiểu
maximum recursion depth exceeded in comparison
03 với trình bao bọc
maximum recursion depth exceeded in comparison
7, nhưng chúng tôi cảm thấy ký hiệu
maximum recursion depth exceeded in comparison
1 ghi lại ý định rõ ràng hơn

Một ví dụ được đưa ra ở đây

Trong 2]

maximum recursion depth exceeded in comparison
0

Ra[2]

maximum recursion depth exceeded in comparison
7

thông báo.

maximum recursion depth exceeded in comparison
1 sẽ ném qua bất kỳ chức năng trung gian nào, do đó, bất kỳ cuộc gọi không theo đuôi nào (trực tiếp hoặc gián tiếp) đến các chức năng ném này sẽ phải sử dụng bộ bảo vệ
maximum recursion depth exceeded in comparison
07. Sau khi làm việc với một số ví dụ, chúng tôi đã giải quyết rằng cơ chế dựa trên lợi nhuận ban đầu tốt hơn. Các ngoại lệ quá khó quản lý và không thêm nhiều. Để điều chỉnh ví dụ dựa trên lợi nhuận của chúng tôi, vui lòng xem tại đây

Chúng tôi cũng có thể chuyên biệt hóa phương thức cho các cuộc gọi phương thức như chúng tôi trình bày bên dưới. Mẫu chúng tôi đang sử dụng là một mẫu đơn giản. các phương thức kết thúc bằng gạch dưới đưa ra các ngoại lệ thay cho các cuộc gọi đuôi (và chỉ gọi các phiên bản của phương thức gạch dưới) và một phương thức bên ngoài không có gạch dưới thực hiện xử lý ngoại lệ

Trong 3]

maximum recursion depth exceeded in comparison
0

Ra[3]

maximum recursion depth exceeded in comparison
7

Và bạn có nó rồi đấy. loại bỏ cuộc gọi đuôi dựa trên ngoại lệ không gian thấp. Đây là một trong những ý tưởng mà chúng tôi đang xem xét sử dụng để loại bỏ giới hạn duyệt đối tượng được lồng sâu khỏi phiên bản

maximum recursion depth exceeded in comparison
3 sắp tới của
maximum recursion depth exceeded in comparison
09 (phiên bản còn lại là trình lặp truy cập cây không đệ quy)

Tại sao Python không hỗ trợ đệ quy đuôi?

Lý do của giới hạn này là (trong số những lý do khác) việc thực hiện các cuộc gọi đệ quy cần rất nhiều bộ nhớ và tài nguyên vì mỗi khung trong ngăn xếp cuộc gọi phải được duy trì cho đến khi cuộc gọi hoàn tất . .

Đệ quy đuôi có nhanh hơn trong Python không?

Nó sử dụng ít bộ nhớ hơn so với hàm đệ quy không đuôi. Nhanh hơn vì trình biên dịch có thể thực hiện tối ưu hóa đặc biệt cho các lệnh gọi hàm đệ quy đuôi .

Ngôn ngữ nào hỗ trợ đệ quy đuôi?

Loại bỏ đệ quy đuôi là một tính năng rất thú vị có sẵn trong các ngôn ngữ Lập trình hàm, như Haskell và Scala . Nó thực hiện các cuộc gọi hàm đệ quy nhanh như vòng lặp.

Là đệ quy đuôi luôn luôn có thể?

Không phải hàm đệ quy nào cũng có thể biến thành hàm đệ quy đuôi . Cụ thể, nếu một hàm thực hiện lời gọi đệ quy, nhưng sau đó kiểm tra kết quả và thực hiện những việc khác nhau tùy thuộc vào giá trị của nó, thì có thể không thực hiện được hàm đệ quy đuôi.