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

时间:2019-04-21 02:55:22

标签: python stack-overflow turtle-graphics

我制作了一个程序来在Python乌龟中沿随机方向生成线条。我这样做的方式非常重复,而且也会崩溃。我的代码反复通过函数重复执行。我发现它总是在第3215次递归时崩溃。我不知道这是否相关。我在问是否有人知道它为什么崩溃以及如何停止它。崩溃时,乌龟图形窗口和cmd窗口都会随机关闭。 我的代码:

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')

2 个答案:

答案 0 :(得分:2)

每当调用一个函数时,就会在分配给名为stack的程序的有限数据区域中使用内存。如果这些函数从未解析并继续调用其他函数,则将永远不会回收堆栈存储器。最终,程序将耗尽内存。这称为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:无限循环。


正如您提到的,代码中有很多重复。编写一个类来封装乌龟逻辑可提供大量的清理机会,并使该程序易于扩展以处理任意数量的其他乌龟:

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()

快乐的旅行!

答案 1 :(得分:1)

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

由于您没有使用递归的任何优势(在递归之间没有学到任何东西或在递归之间没有传递任何东西),而是要模拟协程,因此,请使用生成器来模拟它们而无需递归:

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()