Модуль turtle и взаимодействие с list & tuple

Есть ли возможность добавления в main файл списка кортежей так, чтобы вызывать черепашку только по индексу?

spisok = [(t.right(90), t.forward(60), t.left(90), t.forward(30), t.left(90), t.forward(60)),
(t.penup(),t.right(45),t.forward(30),t.pendown(),t.left(90), t.forward(30)...)],

после объявления списка обращаться к индексам списка только через input. И отрисовываться будет только вызванный индекс.
Само задание звучит так:

Воспользуйтесь списками кортежей, чтобы задать рисование Черепашкой цифр.

Я не могу придумать конструкцию, где бы черепашка рисовала только тогда, когда я её прошу об этом.


Ответы (3 шт):

Автор решения: Amgarak

Давайте я вам покажу саму идею, дальше додумаете под свои хотелки.

Основная мысль в том, что вы храните в кортеже не результаты вызова методов, а сами методы(ссылки на функции), а так же аргументы для них:

import turtle

t = turtle.Turtle()

list_commands = [
    (t.forward, (100,), t.right, (90,), t.forward, (100,)),  # Набор под индексом 0, и т.д..
]

commands = list_commands[0]

for i in range(0, len(commands), 2):
    method = commands[i]    
    args = commands[i+1]   
    method(*args)

turtle.done()

Алгоритм: выбираем из списка нужный кортеж > итерируемся по нему > вытаскиваем нужный метод и его аргументы > вызываем метод с его аргументами.

З. Ы. Над структурой данных можно конечно отдельно подумать, но думаю основная мысль должна быть понятна.

→ Ссылка
Автор решения: Stanislav Volodarskiy

Если нужно исполнять последовательность действий, оформите её в функцию. Несколько последовательностей соберите в список. Выбирайте функцию из списка по индексу и выполняйте его:

import turtle


def f0(t):
    t.right(90)
    t.forward(60)
    t.left(90)
    t.forward(30)
    t.left(90)
    t.forward(60)


def f1(t):
    t.penup()
    t.right(45)
    t.forward(30)
    t.pendown()
    t.left(90)
    t.forward(30)
    

spisok = [f0, f1]

t = turtle.Turtle()
while True:
    i = int(input())
    f = spisok[i]
    f(t)
$ python interactive.py
1
1
1
1
0
0
0
0

введите сюда описание изображения

→ Ссылка
Автор решения: Vitalizzare ушел в монастырь

Предложу альтернативу в виде команды exec, которую применим к строкам вида "forward(100); left(90); ..." в глобальном пространстве имен модуля turtle. Пример построения синего равностороннего треугольника:

exec("color('blue'); down(); fd(100); lt(120); fd(100); lt(120); fd(100)", globals=turtle.__dict__)

На уровне модуля turtle определены функции прорисовки, которые являются оберткой вокруг одноименных методов используемого по умолчанию объекта класса Turtle. Этот объект хранится как условно приватный атрибут Turtle._pen. Перед выполнением команд, пользовательскую черепашку нужно присвоить этому атрибуту, чтобы именно она делала прорисовку. Исходную черепашку желательно сохранить, предварительно явно инициализировав (по умолчанию там стоит None до первого обращения к любой функции прорисовки на уровне модуля). То есть, минимальная подготовка выглядит так:

default_pen = turtle.getpen()
turtle.Turtle._pen = user_defined_pen

Функции для смены черепашки по умолчанию я не обнаружил. Поэтому используем прямое присваивание.

Как это может выглядеть:

import turtle
from functools import partial

digits = [
    'down(); fd(50); lt(90); fd(100); lt(90); fd(50); lt(90); fd(100); up(); lt(90); fd(50+10)',
    'up(); lt(90); fd(50); rt(45); down(); fd(50*2**0.5); rt(135); fd(100); lt(90); up(); fd(10)',
    'up(); lt(90); fd(100); rt(90); down(); fd(50); rt(90); fd(50); rt(45); fd(50*2**0.5); lt(135); fd(50); up(); fd(10)',
    'lt(45); down(); fd(50*2**0.5); lt(135); fd(50); rt(135); fd(50*2**0.5); lt(135); fd(50); up(); lt(90); fd(100); lt(90); fd(50+10)',
    'up(); lt(90); fd(100); down(); bk(50); rt(90); fd(50); lt(90); fd(50); bk(100); up(); rt(90); fd(10)',
    'down(); fd(50); lt(90); fd(50); lt(90); fd(50); rt(90); fd(50); rt(90); fd(50); up(); rt(90); fd(100); lt(90); fd(10);',
    'up(); lt(90); fd(50); rt(90); down(); fd(50); rt(90); fd(50); rt(90); fd(50); rt(90); fd(100); rt(90); fd(50); up(); rt(90); fd(100); lt(90); fd(10);',
    'up(); lt(90); fd(100); rt(90); down(); fd(50); rt(135); fd(50*2**0.5); lt(45); fd(50); up(); lt(90); fd(50+10)',
    'down(); fd(50); lt(90); fd(100); lt(90); fd(50); lt(90); fd(100); bk(50); lt(90); fd(50); up(); rt(90); fd(50); lt(90); fd(10)',
    'down(); fd(50); lt(90); fd(100); lt(90); fd(50); lt(90); fd(50); lt(90); fd(50); up(); rt(90); fd(50); lt(90); fd(10)'
]

digit_dict = dict(zip('0123456789', digits))
run_commands = partial(exec, globals=turtle.__dict__)
default_pen = turtle.getpen()   # initialize and save the link to the default pen
default_pen.pen(pensize=3, speed=10)
red_pen = default_pen.clone()
red_pen.pencolor('red')
green_pen = default_pen.clone()
green_pen.pencolor('green')

position = {
    'default': 'up(); goto(-5*50-4*10, 0)',
    'green': 'up(); goto(-4*50-3*10, -120)',
    'red': 'up(); goto(10, -120)'
}

def draw_number(number: str):
    for digit in number:
        run_commands(digit_dict[digit])

def set_default_pen(pen):
    turtle.Turtle._pen = pen

# Draw all digits with the default pen
run_commands(position['default'])
for digit in digits:
    run_commands(digit)

# Draw the year with the red pen
set_default_pen(red_pen)
run_commands(position['red'])
draw_number('2025')

# Draw the day and month with the green pen
set_default_pen(green_pen)
run_commands(position['green'])
draw_number('0903')

turtle.done()

drawing example

Следующим шагом мы выделим базовые команды и составим из них кортежи, соединяя их через ';'.join(...) в одну исполняемую инструкцию. Например:

import turtle
from contextlib import contextmanager

d = 'down()'
u = 'up()'
e = 'forward(50)'          # edge
de = 'forward(100)'        # double edge
dg = 'forward(50*2**0.5)'  # diagonal
s = 'forward(10)'          # space between digits
l1 = 'left(45)'            # one 45 degree turn to the left 
l2 = 'left(90)'            # two 45 degree turns to the left
l3 = 'left(135)'
r1 = 'right(45)'
r2 = 'right(90)'
r3 = 'right(135)'
x0 = 'setx(-5*50-4*10)'
y0 = 'sety(50)'

digits = [
    (d, e, l2, de, l2, e, l2, de, u, l2, e, s),
    (u, l2, e, r1, d, dg, r3, de, u, l2, s),
    (u, l2, de, r2, d, e, r2, e, r1, dg, l3, e, u, s),
    (l1, d, dg, l3, e, r3, dg, l3, e, u, l2, de, l2, e, s),
    (u, e, l2, d, de, u, l2, e, l2, d, e, l2, e, u, r2, e, l2, s),
    (d, e, l2, e, l2, e, r2, e, r2, e, u, r2, de, l2, s),
    (u, l2, e, r2, d, e, r2, e, r2, e, r2, de, r2, e, u, r2, de, l2, s),
    (u, l2, de, r2, d, e, r3, dg, l1, e, u, l2, e, s),
    (d, e, l2, e, l2, e, r2, e, r2, e, r2, e, r2, e, l2, e, u, l2, e, s),
    (d, e, l2, de, l2, e, l2, e, l2, e, u, r2, e, l2, s)
]

run_commands = lambda cmd: exec(';'.join(cmd), globals=turtle.__dict__)
digit_dict = dict(zip('0123456789', digits))
default_pen = turtle.getpen()   # initialize and save the link to a default pen
default_pen.pen(pensize=3, speed=10)
red_pen = default_pen.clone()
red_pen.pen(pencolor='red')
position = {
    'origin': (u, x0, y0),
    'newline': (u, x0, r2, s, s, de, l2)
}

def draw_number(number: str):
    for digit in number:
        run_commands(digit_dict[digit])

@contextmanager
def set_default_pen(pen, align=True):
    import sys 
    turtle = sys.modules['turtle'] 
    default_pen = turtle.Turtle._pen
    try:
        pen.up()
        if align:
            pen.goto(default_pen.position())
            pen.setheading(default_pen.heading())
        turtle.Turtle._pen = pen
        yield
    finally:
        if align:
            default_pen.up()
            default_pen.goto(pen.position())
            default_pen.setheading(pen.heading())
        turtle.Turtle._pen = default_pen

# Draw all digits with the default pen
run_commands(position['origin'])
for digit in digits:
    run_commands(digit)
# Draw the date with the red pen
with set_default_pen(red_pen):
    run_commands(position['newline'])
    draw_number('09032025')

turtle.done()

drawing example


p.s. Замечание о безопасности. Пока вы контролируете строки, передаваемые в exec, в нем нет ничего плохого. При передаче функции run_commands пользователю, мы можем подстраховаться, проверяя перед выполнением, является ли содержимое кортежей базовыми командами. Например, мы могли бы создать такой модуль draw_digits.py:

# draw_digits.py

from collections.abc import Iterable
import turtle

basic_commands = dict(
      d = 'down()'
    , u = 'up()'
    , e = 'forward(50)'          # edge
    , de = 'forward(100)'        # double edge
    , dg = 'forward(50*2**0.5)'  # diagonal
    , s = 'forward(10)'          # space between digits
    , l1 = 'left(45)'            # one 45 degree turn to the left 
    , l2 = 'left(90)'            # two 45 degree turns to the left
    , l3 = 'left(135)'
    , r1 = 'right(45)'
    , r2 = 'right(90)'
    , r3 = 'right(135)'
    , x0 = 'setx(-5*50-4*10)'
    , y0 = 'sety(50)'
)

globals().update(basic_commands)
__all__ = [*basic_commands, 'basic_commands', 'run_commands']
_basic_command_set = frozenset(basic_commands.values())

def run_commands(commands: Iterable[str]):
    commands = [*commands]     # in case if commands is a generator
    assert _basic_command_set.issuperset(commands), 'Only basic commands are allowed'
    exec(';'.join(commands), globals=turtle.__dict__)

И тогда пользовательская часть может выглядеть так:

from draw_digits import *

hi = (d, l2, de, l2, l2, e, l2, e, l2, e, l2, l2, de, l2, u, s, d, l2, de)
run_commands(hi)

try:
    run_commands(['import os', 'os.system("ls")'])
except Exception as e:
    print(repr(e))
→ Ссылка