Python Черепаха перемещается по списку координат по таймеру - PullRequest
0 голосов
/ 12 марта 2020

Я пытаюсь заставить Python Turtle перемещаться по списку координат, используя время. Вероятно, есть много способов сделать это, но с моей текущей попыткой программа просто зависает. Может кто-нибудь объяснить, почему, пожалуйста?

import turtle

path = [(0, 0), (10, 10), (10, 20), (30, 40)]
bob = turtle.Turtle("square")

def do_path(a_list):
    x, y = a_list[0]
    bob.goto(x, y)
    while len(a_list) > 0:
        turtle.ontimer(lambda: do_path(a_list[1:]), 500)

do_path(path)
turtle.done()

Использование глобальной переменной, похоже, тоже не помогает:

import turtle

path = [(0, 0), (10, 10), (10, 20), (30, 40)]
bob = turtle.Turtle("square")

def do_path():
    global path
    x, y = path.pop(0)
    bob.goto(x, y)
    while len(path) > 0:
        turtle.ontimer(lambda: do_path(path), 500)

do_path()
turtle.done()

Ответы [ 2 ]

1 голос
/ 13 марта 2020

Если бы я хотел это исправить, но сохранил вкус вашего первоначального подхода, чтобы привести его в порядок, я бы использовал partial() из functools вместо lambda (вместе с update_wrapper(), чтобы сделать его приемлемым до ontimer()). Я также удалил бы предположение, что путь начинается с (0, 0) и начинается с пера, опуская его после перехода к первой координате:

from turtle import Screen, Turtle
from functools import partial, update_wrapper

path = [ \
    (100, 180), (90, 170), (95, 150), (80, 135), (65, 145), \
    (55, 140), (70, 125), (65, 105), (75, 85), (60, 70), \
    (70, 60), (80, 75), (100, 65), (120, 75), (130, 60), \
    (140, 70), (125, 85), (135, 105), (130, 125), (145, 140), \
    (135, 145), (120, 135), (105, 150), (110, 170), (100, 180), \
]

def do_path(a_list):
    position, *rest = a_list

    bob.setposition(position)
    bob.pendown()

    if rest:
        wrapper = partial(do_path, rest)
        update_wrapper(wrapper, do_path)
        screen.ontimer(wrapper, 500)
    else:
        bob.hideturtle()

screen = Screen()

bob = Turtle('square')
bob.penup()

do_path(path)

screen.exitonclick()
1 голос
/ 12 марта 2020

Этот рекурсивный вызов через некоторое время l oop кажется мне пугающим - пока l oop никогда не закончится для всех глубин рекурсии, где len(a_list) != 0. Может быть, больше похоже на это?

import turtle

coordinates = [
    (0, 0),
    (10, 10),
    (10, 20),
    (30, 40)
]

coordinates_iter = iter(coordinates)

t = turtle.Turtle("square")

def go_to_next_coord():
    try:
        next_coord = next(coordinates_iter)
    except StopIteration:
        return
    t.goto(next_coord)
    turtle.ontimer(go_to_next_coord, 500)

go_to_next_coord()
turtle.done()

Итак, lambda: do_path (a_list [1:]) не изменяет a_list? При рекурсивном вызове функции это не будет?

Определенно нет! Вы просто нарезаете a_list и передаете этот (полностью независимый) список в do_path в качестве аргумента. a_list с самой первой рекурсии не изменится по размеру, поэтому пока l oop радостно зависает, пока ваш do_path ожидает завершения sh.

EDIT - на topi c или не является ли это действительно "рекурсией":

import turtle

def foo(depth):
    print(f"Starting depth {depth}")
    if depth != 5:
        turtle.ontimer(lambda: foo(depth+1), 1000)
    print(f"Ending depth {depth}")

foo(0)

Вывод:

Starting depth 0
Ending depth 0
Starting depth 1
Ending depth 1
Starting depth 2
Ending depth 2
Starting depth 3
Ending depth 3
Starting depth 4
Ending depth 4
Starting depth 5
Ending depth 5

Похоже, что это технически не является строго рекурсивным вообще! Кажется, у черепахи есть способ планирования этих обратных вызовов. Вывод, который вы можете увидеть в рекурсивной установке, выглядел бы так:

Starting depth 0
Starting depth 1
Starting depth 2
Starting depth 3
Starting depth 4
Starting depth 5
Ending depth 5
Ending depth 4
Ending depth 3
Ending depth 2
Ending depth 1
Ending depth 0

Однако проблема, с которой вы столкнулись, не имеет ничего общего с рекурсией или черепахой в целом. Чтобы быть точным, это связано с неправильным пониманием стека вызовов и / или, возможно, среза списка. Взгляните на этот пример кода:

def do_it(depth, items):
    length = len(items)
    print(f"I'm recursion depth {depth}, I see {length} item(s).")
    if depth != 5: #arbitrary base case:
        new_items = items[1:]
        print(f"Depth {depth} - items: {items}")
        print(f"Depth {depth} - new_items: {new_items}")
        do_it(depth+1, new_items)
    print(f"Depth {depth} is ending now, length is {length} and items is {items}")

do_it(0, [1, 2, 3, 4, 5])

Вывод:

I'm recursion depth 0, I see 5 item(s).
Depth 0 - items: [1, 2, 3, 4, 5]
Depth 0 - new_items: [2, 3, 4, 5]
I'm recursion depth 1, I see 4 item(s).
Depth 1 - items: [2, 3, 4, 5]
Depth 1 - new_items: [3, 4, 5]
I'm recursion depth 2, I see 3 item(s).
Depth 2 - items: [3, 4, 5]
Depth 2 - new_items: [4, 5]
I'm recursion depth 3, I see 2 item(s).
Depth 3 - items: [4, 5]
Depth 3 - new_items: [5]
I'm recursion depth 4, I see 1 item(s).
Depth 4 - items: [5]
Depth 4 - new_items: []
I'm recursion depth 5, I see 0 item(s).
Depth 5 is ending now, length is 0 and items is []
Depth 4 is ending now, length is 1 and items is [5]
Depth 3 is ending now, length is 2 and items is [4, 5]
Depth 2 is ending now, length is 3 and items is [3, 4, 5]
Depth 1 is ending now, length is 4 and items is [2, 3, 4, 5]
Depth 0 is ending now, length is 5 and items is [1, 2, 3, 4, 5]
>>> 

Я знаю, что вывод является немного плотным, чтобы следовать, но, надеюсь, он должен продемонстрировать неправильное представление, которое вы, кажется, иметь. Тот факт, что вы вызываете новую функцию (или ту же функцию в случае рекурсии) внутри функции, не означает, что функция, которую вы «покидаете», заканчивается или завершается. Функция, которую вы оставили, ожидает в стеке вызовов, пока функция, к которой вы пошли, завершается, а затем выполнение возвращается к вызывающей функции. Все, что я действительно пытаюсь подчеркнуть, это то, что разные «глубины» (функции, находящиеся в стеке вызовов) видят разные вещи. Пример, который я использовал здесь, является рекурсивным, но то же самое применимо и в вашем нерекурсивном случае. То, что вы позвонили do_path внутри do_path, не означает, что старый do_path внезапно уходит. Он ожидает внутреннего, самого последнего вызова do_path в fini sh, пока не сможет завершиться sh.

...