Python 中的 Turtle 程序因堆栈溢出而崩溃

Turtle program in Python crashes with stack overflow

我制作了一个程序,可以在 Python 海龟中生成随机方向的线条。我这样做的方式非常重复,而且还崩溃。我的代码通过函数重复递归。我发现它总是在大约第 3215 次递归时崩溃。我不知道这是否相关。我在问是否有人知道它崩溃的原因以及如何阻止它。当它崩溃时,turtle graphics window 和 cmd window 都随机关闭。 我的代码:

import turtle
import random
import sys

sys.setrecursionlimit(100000)

rminlength = 1
rmaxlength = 15
gminlength = 1
gmaxlength = 30
bminlength = 1
bmaxlength = 45

rminangle = 45
rmaxangle = 45
gminangle = 90
gmaxangle = 90
bminangle = 135
bmaxangle = 135

drawspeed = 10000
global recurse
recurse = 0

r = turtle.Turtle()
r.color('red')
r.pensize(3)
r.shape('circle')
r.speed(drawspeed)
r.hideturtle()

g = turtle.Turtle()
g.color('green')
g.pensize(3)
g.shape('circle')
g.speed(drawspeed)
g.hideturtle()

b = turtle.Turtle()
b.color('blue')
b.pensize(3)
b.shape('circle')
b.speed(drawspeed)
b.hideturtle()

#Movement

def rmove():
    if(random.randint(1,2) == 1):
        r.left(random.randint(rminangle,rmaxangle))
        if(random.randint(1,2) == 1):
            r.forward(random.randint(rminlength,rmaxlength))
        else:
            r.backward(random.randint(rminlength,rmaxlength))
    else:
        r.right(random.randint(rminangle,rmaxangle))
        if(random.randint(1,2) == 1):
            r.forward(random.randint(rminlength,rmaxlength))
        else:
            r.backward(random.randint(rminlength,rmaxlength))
    global recurse
    recurse+=1
    print(recurse)
    gmove()

def gmove():
    if(random.randint(1,2) == 1):
        g.left(random.randint(gminangle,gmaxangle))
        if(random.randint(1,2) == 1):
            g.forward(random.randint(gminlength,gmaxlength))
        else:
            g.backward(random.randint(gminlength,gmaxlength))
    else:
        g.right(random.randint(gminangle,gmaxangle))
        if(random.randint(1,2) == 1):
            g.forward(random.randint(gminlength,gmaxlength))
        else:
            g.backward(random.randint(gminlength,gmaxlength))
    global recurse
    recurse+=1
    print(recurse)
    bmove()

def bmove():
    if(random.randint(1,2) == 1):
        b.left(random.randint(bminangle,bmaxangle))
        if(random.randint(1,2) == 1):
            b.forward(random.randint(bminlength,bmaxlength))
        else:
            b.backward(random.randint(bminlength,bmaxlength))
    else:
        b.right(random.randint(bminangle,bmaxangle))
        if(random.randint(1,2) == 1):
            b.forward(random.randint(bminlength,bmaxlength))
        else:
            b.backward(random.randint(bminlength,bmaxlength))
    global recurse
    recurse+=1
    print(recurse)
    rmove()

rmove()
input('Crashed')

无论何时调用函数,都在分配给名为 stack. If these functions never resolve and continue calling other functions, the stack memory is never reclaimed. Eventually, your program runs out of memory. This is called a stack overflow 的程序的有限数据区域中使用内存。

这是您程序的实际错误消息:

  ...
  File "a.py", line 100, in bmove
    rmove()
  File "a.py", line 64, in rmove
    gmove()
  File "a.py", line 82, in gmove
    bmove()
  File "a.py", line 99, in bmove
    print(recurse)
MemoryError: stack overflow

您试图通过增加 Python 设置的递归限制来解决问题,但这只会推迟不可避免的事情。即使一些调用确实解决了,增加这个数字也是一种不安全的代码编写方式,因为它对堆栈大小做出假设而不是重写程序逻辑来确保调用解决并且堆栈不会失去控制。

因为递归对于获得您想要的连续海龟移动是不必要的,让我们重新编写您的程序以使用循环而不是函数调用来引导海龟:

import turtle
import random
import sys


rminlength = 1
rmaxlength = 15
gminlength = 1
gmaxlength = 30
bminlength = 1
bmaxlength = 45

rminangle = 45
rmaxangle = 45
gminangle = 90
gmaxangle = 90
bminangle = 135
bmaxangle = 135

drawspeed = 10000

r = turtle.Turtle()
r.color('red')
r.pensize(3)
r.shape('circle')
r.speed(drawspeed)
r.hideturtle()

g = turtle.Turtle()
g.color('green')
g.pensize(3)
g.shape('circle')
g.speed(drawspeed)
g.hideturtle()

b = turtle.Turtle()
b.color('blue')
b.pensize(3)
b.shape('circle')
b.speed(drawspeed)
b.hideturtle()

#Movement

def rmove():
    if(random.randint(1,2) == 1):
        r.left(random.randint(rminangle,rmaxangle))
        if(random.randint(1,2) == 1):
            r.forward(random.randint(rminlength,rmaxlength))
        else:
            r.backward(random.randint(rminlength,rmaxlength))
    else:
        r.right(random.randint(rminangle,rmaxangle))
        if(random.randint(1,2) == 1):
            r.forward(random.randint(rminlength,rmaxlength))
        else:
            r.backward(random.randint(rminlength,rmaxlength))

def gmove():
    if(random.randint(1,2) == 1):
        g.left(random.randint(gminangle,gmaxangle))
        if(random.randint(1,2) == 1):
            g.forward(random.randint(gminlength,gmaxlength))
        else:
            g.backward(random.randint(gminlength,gmaxlength))
    else:
        g.right(random.randint(gminangle,gmaxangle))
        if(random.randint(1,2) == 1):
            g.forward(random.randint(gminlength,gmaxlength))
        else:
            g.backward(random.randint(gminlength,gmaxlength))

def bmove():
    if(random.randint(1,2) == 1):
        b.left(random.randint(bminangle,bmaxangle))
        if(random.randint(1,2) == 1):
            b.forward(random.randint(bminlength,bmaxlength))
        else:
            b.backward(random.randint(bminlength,bmaxlength))
    else:
        b.right(random.randint(bminangle,bmaxangle))
        if(random.randint(1,2) == 1):
            b.forward(random.randint(bminlength,bmaxlength))
        else:
            b.backward(random.randint(bminlength,bmaxlength))

while 1: # loop infinitely
    rmove()
    gmove()
    bmove()

具体来说,删除了递归调用并添加了 while 1: 无限循环。


如您所说,代码中有很多重复。编写一个 class 来封装你的海龟逻辑提供了一个重要的清理机会,并使程序易于扩展以处理任意数量的额外海龟:

import turtle
from random import choice
from random import randint


class Turtle:
    def __init__(
        self, color, min_len, max_len, angle, speed=10, pensize=3
    ):
        self.min_len = min_len
        self.max_len = max_len
        self.angle = angle
        self.turt = turtle.Turtle()
        self.turt.color(color)
        self.turt.pensize(pensize)
        self.turt.speed(speed)
        self.turt.hideturtle()

    def move(self):
        choice((self.turt.left, self.turt.right))(self.angle)
        dir_func = choice((self.turt.forward, self.turt.backward))
        dir_func(randint(self.min_len, self.max_len))


if __name__ == "__main__":
    turtles = [
        Turtle("red", 1, 15, 45),
        Turtle("green", 1, 30, 90),
        Turtle("blue", 1, 45, 135)
    ]

    while 1:
        for turt in turtles:
            turt.move()

龟龟快乐!

在我的系统上,您的代码调用超过 4000 次而没有崩溃。但重点是,尽管在具有各种设置 setrecursionlimit() 的每个系统上崩溃的位置会有所不同,但它最终会崩溃。

由于除了模拟 coroutines 之外你没有使用递归来获得任何优势(在递归之间没有学习或传递任何东西),让我们使用生成器在没有递归的情况下模拟它们:

from turtle import Screen, Turtle
from random import randint, choice

R_MIN_LENGTH, R_MAX_LENGTH = 1, 15
G_MIN_LENGTH, G_MAX_LENGTH = 1, 30
B_MIN_LENGTH, B_MAX_LENGTH = 1, 45

R_MIN_ANGLE, R_MAX_ANGLE = 45, 45
G_MIN_ANGLE, G_MAX_ANGLE = 90, 90
B_MIN_ANGLE, B_MAX_ANGLE = 135, 135

R_LIMIT = 1500
G_LIMIT = 1250
B_LIMIT = 1000

DRAW_SPEED = 'fastest'

# Movement

def rmove(turtle):
    count = 0

    while count < R_LIMIT:

        if choice([True, False]):
            turtle.left(randint(R_MIN_ANGLE, R_MAX_ANGLE))

            if choice([True, False]):
                turtle.forward(randint(R_MIN_LENGTH, R_MAX_LENGTH))
            else:
                turtle.backward(randint(R_MIN_LENGTH, R_MAX_LENGTH))
        else:
            turtle.right(randint(R_MIN_ANGLE, R_MAX_ANGLE))

            if choice([True, False]):
                turtle.forward(randint(R_MIN_LENGTH, R_MAX_LENGTH))
            else:
                turtle.backward(randint(R_MIN_LENGTH, R_MAX_LENGTH))

        count += 1
        yield count

def gmove(turtle):
    count = 0

    while count < G_LIMIT:

        if choice([True, False]):
            turtle.left(randint(G_MIN_ANGLE, G_MAX_ANGLE))

            if choice([True, False]):
                turtle.forward(randint(G_MIN_LENGTH, G_MAX_LENGTH))
            else:
                turtle.backward(randint(G_MIN_LENGTH, G_MAX_LENGTH))
        else:
            turtle.right(randint(G_MIN_ANGLE, G_MAX_ANGLE))

            if choice([True, False]):
                turtle.forward(randint(G_MIN_LENGTH, G_MAX_LENGTH))
            else:
                turtle.backward(randint(G_MIN_LENGTH, G_MAX_LENGTH))

        count += 1
        yield count

def bmove(turtle):
    count = 0

    while count < B_LIMIT:
        if choice([True, False]):
            turtle.left(randint(B_MIN_ANGLE, B_MAX_ANGLE))

            if choice([True, False]):
                turtle.forward(randint(B_MIN_LENGTH, B_MAX_LENGTH))
            else:
                turtle.backward(randint(B_MIN_LENGTH, B_MAX_LENGTH))
        else:
            turtle.right(randint(B_MIN_ANGLE, B_MAX_ANGLE))

            if choice([True, False]):
                turtle.forward(randint(B_MIN_LENGTH, B_MAX_LENGTH))
            else:
                turtle.backward(randint(B_MIN_LENGTH, B_MAX_LENGTH))

        count += 1
        yield count

r = Turtle('circle', visible=False)
r.color('red')
r.pensize(3)
r.speed(DRAW_SPEED)
red = rmove(r)

g = Turtle('circle', visible=False)
g.color('green')
g.pensize(3)
g.speed(DRAW_SPEED)
green = gmove(g)

b = Turtle('circle', visible=False)
b.color('blue')
b.pensize(3)
b.speed(DRAW_SPEED)
blue = bmove(b)

# written this way so each turtle can have it's own independent limit, as desired
while next(red, R_LIMIT) + next(green, G_LIMIT) + next(blue, B_LIMIT) < R_LIMIT + G_LIMIT + B_LIMIT:
    pass

screen = Screen()
screen.exitonclick()