Модуль 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 шт):
Давайте я вам покажу саму идею, дальше додумаете под свои хотелки.
Основная мысль в том, что вы храните в кортеже не результаты вызова методов, а сами методы(ссылки на функции), а так же аргументы для них:
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()
Алгоритм: выбираем из списка нужный кортеж > итерируемся по нему > вытаскиваем нужный метод и его аргументы > вызываем метод с его аргументами.
З. Ы. Над структурой данных можно конечно отдельно подумать, но думаю основная мысль должна быть понятна.
Если нужно исполнять последовательность действий, оформите её в функцию. Несколько последовательностей соберите в список. Выбирайте функцию из списка по индексу и выполняйте его:
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
Предложу альтернативу в виде команды 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()
Следующим шагом мы выделим базовые команды и составим из них кортежи, соединяя их через ';'.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()
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))


