Программа Turtle в Python падает с переполнением стека - PullRequest
0 голосов
/ 21 апреля 2019

Я сделал программу для генерации линий в произвольных направлениях в Python Turtle.То, как я это сделал, очень повторяющееся, и оно тоже вылетает.Мой код повторяется через функции многократно.Я обнаружил, что он всегда падает примерно на 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 ]

2 голосов
/ 21 апреля 2019

Каждый раз, когда вы вызываете функцию, вы используете память в конечной области данных, выделенных программе, называемой стек . Если эти функции никогда не разрешают и продолжают вызывать другие функции, память стека никогда не восстанавливается. В конце концов, вашей программе не хватает памяти. Это называется переполнением стека .

Вот фактическое сообщение об ошибке из вашей программы:

  ...
  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 голос
/ 21 апреля 2019

В моей системе ваш код выходит за рамки 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()
...