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

问题描述 投票:0回答:2

我做了一个程序,在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')
python stack-overflow turtle-graphics
2个回答
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
投票

在我的系统上,您的代码超过了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()
© www.soinside.com 2019 - 2024. All rights reserved.