Python Turtle: от простых фигур до фракталов
Слышали о черепахе? Если да, то, наверное, считаете её исключительно инструментом для обучения детей. Давайте посмотрим на неё под другим углом. Ведь это не просто способ нарисовать пару линий; это очень наглядный инструмент для освоения ключевых концепций программирования на Python, способный создавать как простейшие фигуры, так и довольно сложные, интерактивные визуализации.
В отличие от абстрактных консольных упражнений, Python Turtle дает мгновенную визуальную обратную связь. Вы пишете код, а на экране сразу появляется рисунок. Именно благодаря этой наглядности модуль Turtle стал классикой для первого знакомства с циклами, функциями, условиями и даже рекурсией.
В этой статье мы разберемся, как управлять нашей виртуальной черепашкой, научим ее рисовать базовые фигуры, а затем перейдем к более сложным узорам с помощью циклов и функций. И вишенкой на торте 🍒 станет погружение в мир рекурсии, где мы заставим черепашку создавать фрактальные деревья и снежинки Коха🐢💨
Что за черепаха и откуда она вышла?
Для тех, кто не знает, Python Turtle – это встроенный в Python модуль, который позволяет вам создавать графику, управляя виртуальной "черепашкой" (курсором) на специальном холсте. Вы даете команды: "иди вперед", "поверни налево", "опусти перо", "нарисуй круг" – и черепашка их выполняет, оставляя за собой след.
Если копнуть в историю, то концепция "черепашьей графики" далеко не нова и уж точно не изобретение Python. Её корни уходят в конец 1960-х годов, к языку программирования Logo, разработанному Сеймуром Пейпертом и его командой в MIT. Logo задумывался как образовательный инструмент, помогающий детям интуитивно осваивать программирование и математические идеи через интерактивное взаимодействие. Изначально "черепашка" была даже физическим роботом, ползающим по полу с маркером! 🤖 Позже эта идея перекочевала на экраны компьютеров. Python унаследовал эту концепцию, предоставив модуль turtle
в своей стандартной библиотеке. 🐢
Основные компоненты, с которыми мы будем работать:
- Черепашка (объект Turtle): наш исполнитель. Обладает текущими координатами на холсте, направлением "взгляда" и состоянием пера (рисует или нет).
- Холст (объект Screen): наше рабочее пространство. Имеет свою систему координат (по умолчанию центр — это (0,0)), фон и реагирует на некоторые события.
- Команды (функции модуля turtle): наш язык общения с черепашкой. Например, turtle.forward(100) или turtle.left(90).
Почему Turtle – отличный старт для изучения Python?
Эта "игрушечная" на первый взгляд система на самом деле является превосходной средой для освоения и оттачивания навыков программирования:
- Простота старта: Модуль turtle – часть стандартной библиотеки Python. Никаких дополнительных установок! Просто import turtle, и вы уже можете начинать рисовать.
- Визуализация абстракций: Как работает цикл for? Увидьте, как он последовательно рисует стороны многоугольника. Что такое функция? Создайте блок кода, рисующий звезду, и вызывайте его многократно в разных местах. Turtle превращает абстрактные концепции (переменные для длин и углов, циклы для повторений, функции для переиспользования, условия для выбора пути) в конкретные, видимые результаты.
- Развитие алгоритмического мышления: Чтобы нарисовать даже несложный узор, вам придется декомпозировать задачу: разбить ее на последовательность элементарных шагов для черепашки. Это и есть тренировка одного из важнейших навыков программиста.
Подготовка к работе: все, что нужно для старта
Теперь давайте перейдем к практике. Одно из главных удобств модуля turtle
– он входит в стандартную библиотеку Python. Это означает, что если у вас установлен Python (а он у вас, скорее всего, установлен, раз вы читаете эту статью 😉), то ничего дополнительно скачивать и устанавливать не нужно. Модуль turtle уже готов к работе "из коробки".
Чтобы начать использовать возможности Turtle, первым делом нужно импортировать сам модуль. Делается это стандартной командой Python: import turtle
.
Теперь создадим две главные сущности: экран (холст, где будет происходить всё волшебство) и, собственно, саму черепашку:
# Создаем экран (холст) screen = turtle.Screen() screen.title("Моя первая черепаха!") # Задаем заголовок окна # Создаем черепашку leonardo = turtle.Turtle() # Назовем её Леонардо, в честь... ну вы поняли 🐢
screen = turtle.Screen()
: Эта команда создает окно, в котором будет рисовать наша черепашка. Мы можем настраивать его свойства, например, заголовок, как показано строкой ниже.leonardo = turtle.Turtle()
: Так мы создаем сам объект черепашки. По умолчанию она появляется в центре экрана (в точке с координатами (0,0)) и смотрит вправо. Вы можете создать несколько черепашек, дав им разные имена. Имя переменной для черепашки, конечно, может быть любым, многие по традиции называют ее простоt
.
Если вы запустите этот код, то увидите окно с заголовком "Моя первая черепаха!" и маленькой стрелочкой-черепашкой в центре. Но есть нюанс: окно, скорее всего, тут же закроется! Чтобы оно оставалось открытым и ждало наших дальнейших команд, нужно добавить в конец скрипта специальную команду:
screen.mainloop() # Или её псевдоним turtle.done()
Эта команда запускает цикл обработки событий для окна Turtle
и не дает программе завершиться сразу после выполнения всех инструкций. Теперь окно останется открытым, пока вы его не закроете вручную.
Полный минимальный код для старта выглядит так:
import turtle # Создаем экран screen = turtle.Screen() screen.title("Моя первая графика черепаха!") # Создаем черепашку leonardo = turtle.Turtle() # Здесь будут команды для Леонардо... # Держим окно открытым screen.mainloop()
С этим разобрались, теперь давайте посмотрим, как ориентироваться в мире, где живет наша черепашка.
Прежде чем отправлять нашего Леонардо (или как вы там назвали свою черепашку) в путешествие, давайте разберемся с картой местности, то есть с устройством холста Screen
.
Начало координат (0,0): По умолчанию, самая середина холста – это точка с координатами (0,0). Именно здесь, как правило, и появляется наша черепашка при рождении, готовая к приключениям.
- Ось X идет горизонтально. Движение вправо от центра увеличивает координату X (положительные значения), движение влево – уменьшает (отрицательные значения).
- Ось Y идет вертикально. Движение вверх от центра увеличивает координату Y (положительные значения), движение вниз – уменьшает (отрицательные значения).
Если вы учили в школе Декартову систему координат, то тут все покажется знакомым. Это она и есть.
Понимание этой системы координат и направлений критически важно для того, чтобы точно управлять черепашкой и рисовать то, что вы задумали. Например, команда my_turtle.forward(100)
заставит черепашку пройти 100 пикселей в том направлении, куда она сейчас смотрит. Если она смотрела вправо (0 градусов), то окажется в точке (100,0).
Основы управления черепашкой
Итак, холст готов, черепашка на старте. Пришло время научить ее двигаться и рисовать! Все управление происходит через вызов методов объекта черепашки (в наших примерах это leonardo
) или функций модуля turtle
напрямую.
Это базовые команды, без которых не обойтись:
leonardo.forward(distance
) илиleonardo.fd(distance)
: Перемещает черепашку вперед на указанноеdistance
(расстояние в пикселях) в текущем направлении.leonardo.backward(distance)
илиleonardo.bk(distance)
или leonardo.back(distance)
: Перемещает черепашку назад на указанноеdistance
.leonardo.left(angle)
илиmy_leonardo.lt(angle)
: Поворачивает черепашку влево наangle
(угол в градусах) относительно ее текущего направления.leonardo.right(angle)
илиleonardo.rt(angle)
: Поворачивает черепашку вправо наangle
.leonardo.goto(x, y)
илиleonardo.setpos(x, y)
, илиleonardo.setposition(x, y)
: Мгновенно перемещает черепашку в точку с абсолютными координатами (x, y). При этом направление черепашки не меняется. Если перо опущено, будет проведена линия от текущей точки до новой.leonardo.setheading(angle)
илиleonardo.seth(angle)
: Устанавливает абсолютное направление черепашки. angle задается в градусах (0 – вправо, 90 – вверх, и т.д., как мы уже обсуждали).
Давайте попробуем нарисовать что-нибудь простое, например, букву "L":
import turtle screen = turtle.Screen() screen.title("Первые штрихи Леонардо") leo = turtle.Turtle() # Сократим имя для удобства в коде # Рисуем вертикальную часть "Г" leo.left(90) # Поворачиваем вверх (изначально смотрит вправо) leo.forward(150) # Рисуем горизонтальную часть "Г" leo.right(90) # Поворачиваем вправо (относительно черепашки, теперь она смотрит вправо по экрану) leo.forward(75) leo.hideturtle() # Спрячем черепашку, чтобы не отвлекало screen.mainloop()
Запустим этот код, и увидим результат:
По умолчанию черепашка рисует при каждом своем движении. Но иногда нам нужно переместить её в другую точку холста, не оставляя следа. Или изменить цвет и толщину линии. Для этого есть специальные команды:
leo.penup()
илиleo.pu()
, илиleo.up()
: Поднимает перо. Черепашка будет двигаться, но не рисовать.leo.pendown()
илиleo.pd()
, илиleo.down()
: Опускает перо. Черепашка снова начнет рисовать при движении.leo.pensize(width)
илиleo.width(width)
: Устанавливает толщину пера в width пикселей.leo.pencolor(color_string_or_rgb_tuple)
: Устанавливает цвет пера.- Цвет можно задавать строкой с названием (например, "red", "blue", "green", "black", "yellow", "purple", "orange" и многие другие стандартные цвета HTML).
- Можно использовать шестнадцатеричный код цвета в строке: "#RRGGBB" (например, "#FF0000" для красного).
- А можно кортежем из трех чисел (R, G, B). По умолчанию эти числа должны быть в диапазоне от 0.0 до 1.0. Например, (1.0, 0.0, 0.0) – это красный.
Важно: Чтобы использовать привычные RGB значения от 0 до 255, нужно предварительно переключить цветовой режим: turtle.colormode(255)
. Тогда красный цвет можно будет задать как (255, 0, 0). Эту команду достаточно выполнить один раз в начале программы.
import turtle screen = turtle.Screen() screen.title("Работа с пером") turtle.colormode(255) # Переключаемся на RGB 0-255 для удобства artist = turtle.Turtle() artist.speed(1) # Помедленнее, чтобы рассмотреть # Рисуем первую, толстую красную линию artist.pensize(10) artist.pencolor("red") # Можно и так: artist.pencolor((255, 0, 0)) artist.forward(100) # Перемещаемся на новую позицию без рисования artist.penup() artist.goto(0, 50) # Переходим на 50 пикселей выше по Y, X оставляем 0 artist.pendown() # Рисуем вторую, тонкую синюю линию artist.pensize(3) artist.pencolor((0, 0, 255)) # Синий в RGB artist.forward(100) artist.hideturtle() screen.mainloop()
Базовые строительные блоки: рисуем простые формы
Прямые линии – это, конечно, хорошо, но мир не состоит из одних отрезков. Наша черепашка Леонардо умеет рисовать и кое-что поинтереснее!
- Линии: С ними мы уже разобрались – это результат команд
forward()
,backward()
иgoto()
, когда перо опущено. Это наш самый базовый "мазок". - Круги и дуги: l
eo.circle(radius, extent=None, steps=None)
Эта команда рисует окружность или её часть (дугу). Давайте разберем её параметры: radius
: Это радиус окружности. Еслиradius
положительный, центр окружности будет слева от черепашки (она начнет рисовать дугу, двигаясь против часовой стрелки). Еслиradius
отрицательный, центр будет справа (движение по часовой стрелке).extent
(необязательный): Угол в градусах, который определяет, какую часть окружности рисовать. Если не указан, рисуется полная окружность (360 градусов). Например,extent=180
нарисует полуокружность.steps
(необязательный): Окружность на самом деле аппроксимируется (приближенно рисуется) набором коротких прямых отрезков. Этот параметр позволяет указать, сколько таких отрезков использовать. Чем больше steps, тем более гладкой будет окружность, но и рисоваться будет чуть дольше. Если не указан,Turtle
подбирает значение автоматически. Иногда этот параметр используют для рисования правильных многоугольников, вписанных в окружность (например,steps=6
для шестиугольника).- Точки:
leo.dot(size=None, *color)
Рисует закрашенный круг (точку) в текущей позиции черепашки. size
(необязательный): Диаметр точки в пикселях. Если не указан, используется значение, зависящее от толщины пера (pensize
), обычноmax(pensize + 4, 2 * pensize)
.*color
(необязательный): Цвет точки. Можно передать строкой ("blue") или RGB-кортежем (еслиcolormode(255)
активен, то (0, 0, 255)). Если цвет не указан, используется текущий pencolor черепашки.
Давайте посмотрим на эти команды в действии:
import turtle screen = turtle.Screen() screen.title("Круги и точки от Леонардо") turtle.colormode(255) leo = turtle.Turtle() leo.speed(3) # Чуть помедленнее для наглядности # Рисуем зеленый круг leo.penup() leo.goto(-100, 0) # Сместимся немного влево, чтобы было место leo.pendown() leo.pencolor("darkgreen") leo.pensize(3) leo.circle(80) # Радиус 80 # Рисуем красную дугу (полуокружность) leo.penup() leo.goto(100, 100) leo.pendown() leo.pencolor("red") leo.pensize(2) leo.circle(60, 180) # Радиус 60, дуга 180 градусов # Ставим несколько точек разного размера и цвета leo.penup() leo.goto(0, -150) leo.pendown() leo.dot(30, "blue") # Синяя точка диаметром 30 leo.penup() leo.goto(50, -150) leo.pendown() leo.pencolor("orange") # Установим цвет пера leo.dot() # Точка текущим цветом (оранжевым) и размером по умолчанию leo.hideturtle() screen.mainloop()
Сможете нарисовать смайлик? Попробуйте, а затем продолжим 😉
Заливка фигур
До сих пор наши окружности и линии были лишь контурами. Но Turtle позволяет "заливать" замкнутые фигуры цветом, делая рисунки объемнее и ярче. Для этого есть три ключевые команды, которые обычно используются вместе:
leo.fillcolor(color_string_or_rgb_tuple)
: Эта команда устанавливает цвет заливки. Работает точно так же, как pencolor(): можно использовать названия цветов строкой ("yellow", "#FFFF00") или RGB-кортежи (например, (255, 255, 0) для желтого, если активенturtle.colormode(255)
).leo.begin_fill()
: Эту команду нужно вызвать перед тем, как вы начнете рисовать замкнутый контур, который хотите залить. Она как бы говорит черепашке: "Запомни это место, отсюда начнется область для заливки".leo.end_fill()
: Эту команду вызывают после того, как контур фигуры полностью нарисован и замкнут. Черепашка соединит текущую точку с той, где был вызван begin_fill(), и зальет получившуюся область выбраннымfillcolor()
.
Важно: Чтобы заливка сработала корректно, фигура, которую вы рисуете между begin_fill()
и end_fill()
, должна образовывать замкнутый контур. Если вы, например, просто нарисуете линию и вызовете end_fill()
, то Turtle попытается соединить конец линии с начальной точкой (где был begin_fill()
) и зальет получившийся треугольник или более сложную фигуру, что может быть не тем, чего вы ожидали.
Давайте нарисуем простой желтый круг с синим контуром:
import turtle screen = turtle.Screen() screen.title("Заливаем фигуры цветом!") turtle.colormode(255) leo = turtle.Turtle() leo.speed(3) # Настройки для круга radius = 70 pen_color = "blue" fill_color_circle = (255, 255, 0) # Желтый leo.penup() leo.goto(0, -radius) # Смещаемся вниз, чтобы круг был по центру leo.pendown() # Устанавливаем цвета leo.pencolor(pen_color) leo.fillcolor(fill_color_circle) leo.pensize(4) # Начинаем запись для заливки leo.begin_fill() # Рисуем сам круг leo.circle(radius) # Заканчиваем запись и заливаем leo.end_fill() # Теперь попробуем нарисовать простой закрашенный квадрат # без использования циклов (их мы изучим чуть позже) # чтобы продемонстрировать замыкание контура. leo.penup() leo.goto(-150, -50) leo.pendown() side_length = 100 square_fill_color = "lightgreen" leo.pencolor("darkgreen") leo.fillcolor(square_fill_color) leo.pensize(2) leo.begin_fill() leo.forward(side_length) leo.left(90) leo.forward(side_length) leo.left(90) leo.forward(side_length) leo.left(90) leo.forward(side_length) leo.left(90) # Замыкающий поворот важен для корректного вида контура leo.end_fill() leo.hideturtle() screen.mainloop()
Теперь у yас есть настоящий закрашенный круг и квадрат. Можете вернуться к идее со смайликом и попробовать сделать ему желтое "лицо". 😉
Настраиваем черепашку и холст
Мы уже умеем двигать черепашку, рисовать линии, круги, точки и даже заливать фигуры цветом. Но есть еще несколько полезных настроек, которые помогут сделать наши рисунки и сам процесс их создания более контролируемым и приятным.
Управление скоростью черепашки
Иногда черепашка рисует слишком быстро, и мы не успеваем насладиться процессом или отследить, как именно строится фигура. А порой, наоборот, хочется, чтобы сложный узор появился на экране мгновенно. Для этого есть команда leo.speed(speed_value)
:
speed_value
может быть числом от 0 до 10.- 1 – самая медленная скорость.
- 10 – быстрая скорость.
- 0 – самая быстрая (часто означает "мгновенно", без видимой анимации трассировки каждого шага).
- Вместо чисел можно использовать строки-псевдонимы:
По умолчанию скорость черепашки установлена в 'normal' (или 6). Для отладки сложных рисунков удобно ставить 'slowest' или 'slow', а для финальной демонстрации – 'fastest'.
Внешний вид черепашки
Стандартная стрелочка, конечно, хороша, но иногда хочется разнообразия или просто, чтобы курсор лучше соответствовал теме рисунка.
Вы можете попробовать разные и выбрать ту, что больше нравится.
leo.hideturtle(
) илиleo.ht()
: Делает курсор черепашки невидимым. Это очень полезно, когда сам курсор начинает мешать восприятию готового рисунка, особенно если он большой или сложный. Мы уже использовали эту команду в предыдущих примерах.leo.showturtle()
илиleo.st()
: Снова делает черепашку видимой, если она была спрятана.
Настройки холста (экрана)
Мы уже знаем, как менять заголовок окна (screen.title()
). Есть еще одна полезная команда для настройки внешнего вида нашего холста:
screen.bgcolor(color_string_or_rgb_tuple)
: Устанавливает цвет фона для холста. Принимает те же форматы цвета, что и pencolor()
или fillcolor()
. Например, screen.bgcolor("lightyellow")
или screen.bgcolor((0, 0, 50))
для темно-синего фона (если turtle.colormode(255)
).
Давайте соберем некоторые из этих настроек в одном примере:
import turtle # Настройки экрана screen = turtle.Screen() screen.title("Кастомизация черепашки и холста") screen.bgcolor("lightblue") # Светло-голубой фон turtle.colormode(255) # Для RGB цветов фона, если нужно # Создаем и настраиваем черепашку tina = turtle.Turtle() tina.shape("turtle") # Пусть будет настоящей черепашкой! tina.color("darkgreen") # Цвет самой черепашки (не пера, а иконки) tina.pensize(3) tina.pencolor("black") tina.speed("slow") # Замедляем, чтобы было видно # Нарисуем что-нибудь простое tina.forward(100) tina.left(120) tina.forward(100) tina.left(120) tina.forward(100) tina.left(120) # Нарисовали треугольник # Можно изменить цвет черепашки (иконки) на лету tina.color("purple", "yellow") # Первый цвет - контур иконки, второй - заливка иконки tina.penup() tina.goto(-100, -100) tina.pendown() tina.circle(50) # В конце можно спрятать, если мешает # tina.hideturtle() screen.mainloop()
Обратите внимание, что tina.color("purple", "yellow")
с двумя аргументами меняет цвет самой иконки черепашки (контур и заливку), если форма это поддерживает (например, форма 'turtle'). Если передать один аргумент tina.color("red")
, то он одновременно установит и pencolor
, и fillcolor
для будущих заливок, и цвет иконки черепашки.
С этими настройками у нас появляется больше контроля над визуальной стороной. Теперь давайте посмотрим, как циклы и функциии, позволят нам создавать более сложные и красивые узоры.
От линий к узорам – используем циклы и функции
До сих пор мы рисовали фигуры, отдавая черепашке каждую команду по отдельности. Нарисовать квадрат? Четыре раза forward()
, четыре раза left()
. А если нужен 12-угольник? Или узор из 50 одинаковых элементов? Ручное написание такого кода быстро станет утомительным и подверженным ошибкам.
Повторение – мать рисования: циклы for
в деле
Цикл for
позволяет выполнить один и тот же блок кода определенное количество раз. Это именно то, что нам нужно для рисования правильных многоугольников или повторяющихся узоров!
Классический квадрат (теперь еще и с заливкой!)
Вспомним, как мы рисовали квадрат "в лоб":
leo.forward(100) leo.left(90) leo.forward(100) leo.left(90) leo.forward(100) leo.left(90) leo.forward(100) leo.left(90)
С циклом for
это будет выглядеть так:
import turtle screen = turtle.Screen() screen.title("Квадрат с помощью цикла for") turtle.colormode(255) square_maker = turtle.Turtle() square_maker.shape("classic") square_maker.speed(3) side_length = 150 pen_c = "darkblue" fill_c = (173, 216, 230) # Lightblue square_maker.pencolor(pen_c) square_maker.fillcolor(fill_c) square_maker.pensize(3) square_maker.begin_fill() for _ in range(4): # Повторить следующий блок 4 раза square_maker.forward(side_length) square_maker.left(90) square_maker.end_fill() square_maker.hideturtle() screen.mainloop()
Разберем строку for _ in range(4):
:
for ... in ...:
– это стандартный синтаксис циклаfor
в Python.range(4)
– функцияrange()
генерирует последовательность чисел. range(4) создаст последовательность 0, 1, 2, 3 (всего 4 числа). Цикл выполнится для каждого числа из этой последовательности._
(подчеркивание) – это просто имя переменной, которая на каждой итерации цикла будет принимать очередное значение изrange(4)
. Мы используем подчеркивание как соглашение, когда само значение этой переменной нам внутри цикла не нужно, а важно лишь количество повторений.
Код стал короче, чище, и если нам понадобится изменить длину стороны или угол, это нужно будет сделать только в одном месте!
Рисуем правильный N-угольник
А что если мы хотим нарисовать равносторонний треугольник, пятиугольник, шестиугольник? Логика та же: двигаемся вперед, поворачиваем. Меняется только количество сторон (и, соответственно, количество повторений в цикле) и угол поворота.
Для правильного N-угольника (у которого все N сторон и все N углов равны) внешний угол поворота всегда будет 360 / N градусов.
Давайте напишем код, который нарисует, например, залитый фиолетовый шестиугольник:
import turtle screen = turtle.Screen() screen.title("Правильный N-угольник") turtle.colormode(255) poly_drawer = turtle.Turtle() poly_drawer.speed(5) poly_drawer.hideturtle() # Параметры для нашего многоугольника num_sides = 6 # Количество сторон (попробуйте изменить на 3, 5, 8!) side_len = 80 # Длина одной стороны pen_color = (75, 0, 130) # Indigo fill_color_poly = (147, 112, 219) # MediumPurple poly_drawer.pencolor(pen_color) poly_drawer.fillcolor(fill_color_poly) poly_drawer.pensize(2) # Рассчитываем угол поворота turn_angle = 360 / num_sides poly_drawer.begin_fill() for _ in range(num_sides): poly_drawer.forward(side_len) poly_drawer.left(turn_angle) poly_drawer.end_fill() screen.mainloop()
Попробуйте изменить значение num_sides
на 3, 5, 7, 8 и посмотрите, как легко теперь создавать разные правильные многоугольники!
Принцип DRY (Don't Repeat Yourself) в действии
Использование циклов – это один из первых и важнейших шагов к соблюдению принципа DRY ("Не повторяйся"). Этот принцип гласит, что каждая часть знания (в нашем случае, код для рисования одной стороны и поворота) должна иметь единственное, недвусмысленное, авторитетное представление в системе. Повторение кода (копипаст) – это зло, так как:
- Увеличивает объем кода.
- Затрудняет чтение и понимание.
- Ведет к ошибкам (если нужно внести изменение, легко забыть исправить во всех скопированных местах).
Используем функции
Представьте, что в программе нам нужно нарисовать не один, а, скажем, десять квадратов разного размера, цвета и в разных местах холста. Или, может быть, мы хотим использовать сложную фигуру (например, звезду) многократно в своем узоре. Копировать и вставлять код цикла для рисования квадрата или звезды каждый раз – это снова нарушение принципа DRY и прямой путь к запутанному коду.
Гораздо элегантнее определить функцию, которая будет выполнять определенную задачу (например, рисовать квадрат), а затем вызывать эту функцию столько раз, сколько нужно, возможно, передавая ей нужные аргументы (например, размер и цвет квадрата).
Давайте создадим функцию, которая будет рисовать закрашенный многоугольник (мы уже писали код для этого, теперь "упакуем" его в функцию). Эта функция будет принимать в качестве аргументов объект черепашки, количество сторон, размер, цвет пера, цвет заливки и координаты начальной точки.
import turtle # --- Определение функции --- def draw_filled_polygon(t, sides, size, pen_color, fill_color, x, y): """ Рисует закрашенный правильный многоугольник в указанных координатах. Args: t (turtle.Turtle): Объект черепашки для рисования. sides (int): Количество сторон многоугольника. size (int): Длина одной стороны. pen_color (str or tuple): Цвет пера. fill_color (str or tuple): Цвет заливки. x (int): Координата X начальной точки. y (int): Координата Y начальной точки. """ t.penup() # Поднять перо перед перемещением t.goto(x, y) # Перейти в начальную точку t.pendown() # Опустить перо для рисования t.pencolor(pen_color) t.fillcolor(fill_color) t.pensize(2) turn_angle = 360 / sides t.begin_fill() for _ in range(sides): t.forward(size) t.left(turn_angle) t.end_fill() # --- Основная часть программы --- screen = turtle.Screen() screen.title("Рисуем с помощью функций!") turtle.colormode(255) screen.bgcolor("lightgray") artist = turtle.Turtle() artist.speed("fast") # Для более быстрой отрисовки нескольких фигур artist.hideturtle() # Теперь используем нашу функцию для рисования разных фигур draw_filled_polygon(artist, 6, 50, "darkviolet", "violet", -100, 100) # Фиолетовый шестиугольник draw_filled_polygon(artist, 3, 80, (0,100,0), "lightgreen", 50, 120) # Зеленый треугольник draw_filled_polygon(artist, 8, 40, "navy", "skyblue", -50, -75) # Синий восьмиугольник draw_filled_polygon(artist, 5, 60, "maroon", "pink", 150, -100) # Розовый пятиугольник screen.mainloop()
- Мы определили функцию
draw_filled_polygon
один раз. - Затем мы вызвали её четыре раза с разными аргументами, чтобы нарисовать четыре разные фигуры.
- Код стал намного чище и понятнее. Если мы найдем ошибку в логике рисования многоугольника или захотим что-то улучшить, нам нужно будет исправить это только в одном месте – внутри функции.
Более сложные узоры
Освоив циклы для повторений и функции для структурирования и переиспользования кода, мы можем начать их комбинировать для создания действительно эффектных графических композиций.
Спирали – это всегда красиво и гипнотизирующе. Их легко создавать, постепенно увеличивая длину шага и немного поворачивая на каждой итерации. Добавим еще смену цвета.
import turtle screen = turtle.Screen() screen.bgcolor("black") # Черный фон будет хорошо смотреться spiral_creator = turtle.Turtle() spiral_creator.speed("fastest") # Для спиралей нужна скорость spiral_creator.pensize(2) colors = ["#FF00FF", "#00FFFF", "#FFFF00", "#FF0000", "#00FF00", "#0000FF"] for i in range(200): # Количество витков/шагов спирали spiral_creator.pencolor(colors[i % len(colors)]) # Циклически выбираем цвет spiral_creator.forward(i * 2) # Увеличиваем длину шага на каждой итерации spiral_creator.right(59) # Угол поворота, можно экспериментировать # Попробуйте 90 (квадратная спираль), 120, 144, или даже что-то вроде 91, 89 spiral_creator.hideturtle() screen.mainloop()
В этом примере colors[i % len(colors)]
– это хитрый способ циклически перебирать цвета из списка. Оператор %
(остаток от деления) гарантирует, что индекс никогда не выйдет за пределы списка colors
.
Мы уже использовали цикл for для рисования сторон одной фигуры. А что, если мы хотим нарисовать множество одинаковых фигур, каждая из которых немного повернута относительно предыдущей? Здесь нам поможет вложенный цикл (цикл внутри цикла) или цикл, который на каждой итерации вызывает нашу функцию для рисования фигуры.
Давайте создадим узор из вращающихся квадратов. Для большей эффектности можно менять цвет или размер каждого следующего квадрата.
import turtle import random def draw_fancy_square(t, size, pen_c, fill_c): t.pencolor(pen_c) t.fillcolor(fill_c) t.begin_fill() for _ in range(4): t.forward(size) t.left(90) t.end_fill() screen = turtle.Screen() screen.title("Узор из вращающихся квадратов") screen.bgcolor("gray10") # Темно-серый фон turtle.colormode(255) artist = turtle.Turtle() artist.speed("fastest") artist.hideturtle() artist.penup() artist.goto(0, -150) # Начнем чуть ниже центра для лучшей композиции artist.pendown() num_squares = 36 # Количество квадратов в узоре angle_step = 360 / num_squares # Угол поворота для каждого следующего квадрата initial_size = 120 size_decrement = 3 # На сколько уменьшать квадрат на каждом шаге # Список цветов для градиентного эффекта (оттенки синего/фиолетового) colors_palette = [ (0, 0, 128), (0, 0, 160), (0, 0, 192), (0, 0, 224), (0, 0, 255), (40, 0, 255), (80, 0, 255), (120, 0, 255), (160, 0, 255), (200, 0, 255), (240, 0, 255), (255, 0, 240), (255, 0, 200), (255, 0, 160) ] for i in range(num_squares): current_size = initial_size - (i * size_decrement) if current_size < 10: # Не рисуем слишком маленькие квадраты current_size = 10 # Выбираем цвет из палитры циклически fill_c = colors_palette[i % len(colors_palette)] pen_c = "white" # Контур сделаем белым для контраста draw_fancy_square(artist, current_size, pen_c, fill_c) # Поворачиваем для следующего квадрата artist.left(angle_step) screen.mainloop()
- Внешний цикл
for i in range(num_squares):
отвечает за количество квадратов и поворот между ними. - Внутри цикла мы вызываем
draw_fancy_square
.
За гранью простых фигур: фракталы и рекурсивные узоры
Рекурсия – это концепция в программировании, когда функция вызывает саму себя для решения более мелкой подзадачи той же проблемы. Чтобы рекурсия не была бесконечной, у нее должен быть базовый случай (или условие выхода) – это условие, при котором функция перестает вызывать себя и просто возвращает результат или выполняет простое действие.
Представьте себе матрешку: большая матрешка содержит внутри себя точно такую же, но поменьше, та – еще одну, поменьше, и так далее, пока не дойдем до самой маленькой, которая уже не делится. Это и есть суть рекурсии.
В контексте Turtle, рекурсивная функция может, например, рисовать ветку дерева, а затем для каждой новой, более мелкой ветки, вызывать саму себя.
Ключевые компоненты рекурсивной функции:
- Базовый случай (условие выхода): Что делать, когда задача стала достаточно простой (например, длина ветки слишком мала, чтобы ее рисовать). Без него рекурсия будет бесконечной и приведет к ошибке переполнения стека вызовов (
RecursionError
). - Рекурсивный шаг: Действия, которые выполняет функция, и вызов самой себя с измененными параметрами, приближающими нас к базовому случаю (например, уменьшение длины ветки, уменьшение "порядка" фрактала).
Но прежде чем рисовать что-то сложное рекурсивно, есть один технический момент, который стоит обсудить...
Ускоряем рисование
Когда черепашка рисует что-то сложное, состоящее из множества мелких шагов (а рекурсивные фракталы – это как раз такой случай), анимация каждого отдельного движения может занимать очень много времени. Мы будем видеть, как черепашка медленно, шаг за шагом, вырисовывает каждую линию. Для простых фигур это может быть интересно, но для фрактала, состоящего из тысяч элементов, это превратится в пытку ожиданием. 🐌
К счастью, в Turtle
есть способ ускорить этот процесс:
screen.tracer(n)
: Эта функция управляет тем, как часто обновляется (перерисовывается) экран.- Если
n
– положительное число, то экран будет обновляться после каждых n действий черепашки. Например,screen.tracer(1)
(это значение по умолчанию) означает, что экран обновляется после каждого действия.screen.tracer(10)
будет обновлять экран после каждых 10 действий. - Если
n
равно 0 (screen.tracer(0)
), то автоматическое обновление экрана полностью отключается. Черепашка будет выполнять все команды рисования "в фоновом режиме", в невидимом буфере, а на экране ничего не будет меняться. screen.update()
: Если автоматическое обновление отключено с помощьюscreen.tracer(0)
, то эта команда принудительно обновляет экран, отображая все, что черепашка успела нарисовать в буфере к этому моменту.
Как это использовать для ускорения?
- В самом начале, перед тем как начинать рисовать сложный узор, вызовите
screen.tracer(0)
. - Затем дайте черепашке все команды для рисования вашего узора (например, запустите рекурсивную функцию). Все это будет происходить очень быстро, так как не будет тратиться время на перерисовку экрана после каждого шага.
- После того как все команды рисования выполнены (или на каком-то логическом этапе, если вы хотите показать промежуточный результат), вызовите
screen.update()
. В этот момент весь готовый рисунок мгновенно появится на экране.
Это стандартная практика для рисования сложных сцен в Turtle
, и она значительно сокращает время отрисовки. Мы будем активно использовать screen.tracer(0)
и screen.update()
при рисовании наших фракталов, чтобы не ждать вечность, пока они появятся на экране.
Фрактальное дерево
Фрактальное дерево – один из самых наглядных и красивых примеров использования рекурсии в графике. Идея проста:
- Рисуем "ствол" (прямую линию).
- Из конца этого ствола рекурсивно рисуем две (или более) "ветки" поменьше, отходящие под некоторым углом.
- Каждая из этих веток, в свою очередь, становится "стволом" для своих, еще меньших, дочерних веток.
- Этот процесс повторяется до тех пор, пока ветки не станут слишком короткими (это наш базовый случай).
Давайте определим рекурсивную функцию draw_fractal_tree(branch_len, t, level)
.
branch_len
: текущая длина ветки, которую нужно нарисовать.t
: наш объект черепашки.level
: текущий уровень рекурсии (глубина). Мы можем использовать его для базового случая или для изменения атрибутов (например, толщины веток).
Базовый случай: Если branch_len
становится меньше определенного порога (например, 5 пикселей) или level
достигает нуля, мы перестаем рисовать дальше (просто возвращаемся из функции). Можно на этом этапе нарисовать "листик" – например, зеленую точку.
- Черепашка рисует текущую ветку (
t.forward(branch_len)
). - Сохраняем текущую позицию и направление черепашки. Это важно, чтобы после рисования одной дочерней ветки мы могли вернуться и начать рисовать вторую из той же точки.
- Черепашка поворачивает налево на некоторый угол (например, 20-30 градусов).
- Рекурсивно вызываем
draw_fractal_tree
для левой дочерней ветки. Длина новой ветки будет меньше текущей (например,branch_len * 0.7
). Уровень рекурсии уменьшается на 1. - Черепашка возвращается в сохраненную позицию (где закончился "ствол") и восстанавливает сохраненное направление.
- Черепашка поворачивает направо на тот же угол.
- Рекурсивно вызываем
draw_fractal_tree
для правой дочерней ветки (с такими же уменьшенными параметрами). - Важно! После того как обе дочерние ветки нарисованы, черепашка должна вернуться в начало той ветки, которую она только что рисовала (то есть пройти
branch_len
назад). Это нужно, чтобы родительская ветка, вызвавшая эту функцию, могла корректно продолжить свое выполнение или чтобы следующий рекурсивный вызов на том же уровне начался из правильной точки.
import turtle import random # --- Настройки экрана и черепашки --- screen = turtle.Screen() screen.setup(width=800, height=700) screen.bgcolor("skyblue") turtle.colormode(255) screen.tracer(0) # Отключаем анимацию для быстрой отрисовки tree_drawer = turtle.Turtle() tree_drawer.hideturtle() tree_drawer.speed("fastest") # Хотя с tracer(0) это менее критично tree_drawer.left(90) # Начинаем рисовать ствол снизу вверх tree_drawer.penup() tree_drawer.goto(0, -280) # Начальная позиция ствола пониже tree_drawer.pendown() tree_drawer.pensize(12) # Толстый ствол tree_drawer.pencolor("saddlebrown") # Коричневый цвет для ствола и крупных веток # --- Рекурсивная функция для рисования дерева --- def draw_fractal_tree(branch_len, t, level, angle, thickness_ratio=0.8, length_ratio=0.75): """ Рекурсивно рисует ветку фрактального дерева. branch_len: текущая длина ветки t: объект черепашки level: текущий уровень рекурсии (для контроля глубины и толщины) angle: угол отклонения для новых веток thickness_ratio: коэффициент уменьшения толщины веток length_ratio: коэффициент уменьшения длины веток """ if level == 0 or branch_len < 6: # Базовый случай # Рисуем "листик" original_color = t.pencolor() original_pensize = t.pensize() t.pencolor(random.choice(["green", "darkgreen", "forestgreen", "limegreen"])) t.dot(random.randint(6, 12)) # Случайный размер листика t.pencolor(original_color) # Восстанавливаем цвет пера t.pensize(original_pensize) # Восстанавливаем толщину (на всякий случай) return # Уменьшаем толщину пера для следующих веток # Толщина зависит от уровня рекурсии current_thickness = max(1, int(12 * (thickness_ratio ** (initial_recursion_level - level + 1)))) t.pensize(current_thickness) # Меняем цвет веток на более зеленый по мере уменьшения if level < initial_recursion_level - 2: # Для более мелких веток # Плавный переход от коричневого к зеленому brown_component = max(0, 139 - (initial_recursion_level - level) * 25) green_component = min(255, 100 + (initial_recursion_level - level) * 30) t.pencolor((brown_component, green_component // 2, 0)) # Коричнево-зеленый if level < initial_recursion_level - 4: t.pencolor(random.choice(["olivedrab", "darkolivegreen"])) t.forward(branch_len) # Сохраняем текущую позицию и направление current_pos = t.pos() current_heading = t.heading() # --- Первая дочерняя ветка (например, налево) --- # Добавим немного случайности в угол и длину для "живости" angle_deviation1 = random.uniform(-angle * 0.2, angle * 0.2) length_modifier1 = random.uniform(0.9, 1.1) t.left(angle + angle_deviation1) draw_fractal_tree(branch_len * length_ratio * length_modifier1, t, level - 1, angle, thickness_ratio, length_ratio) # Возвращаемся к развилке для второй ветки t.penup() t.goto(current_pos) t.setheading(current_heading) t.pendown() # --- Вторая дочерняя ветка (например, направо) --- angle_deviation2 = random.uniform(-angle * 0.2, angle * 0.2) length_modifier2 = random.uniform(0.9, 1.1) t.right(angle + angle_deviation2) draw_fractal_tree(branch_len * length_ratio * length_modifier2, t, level - 1, angle, thickness_ratio, length_ratio) # --- (Опционально) Третья, центральная, более короткая ветка для пышности --- if level > 1 and random.random() < 0.3: # Не всегда рисуем третью ветку t.penup() t.goto(current_pos) t.setheading(current_heading) # Направление ствола t.pendown() # Рисуем короткую центральную ветку, если условия позволяют draw_fractal_tree(branch_len * length_ratio * 0.6, t, level - 1, angle, thickness_ratio, length_ratio) # Возвращаемся к началу текущей ветки (важно!) t.penup() t.goto(current_pos) # Это точка, ОТКУДА росли дочерние ветки # Теперь нужно "отползти" назад по только что нарисованной ветке t.setheading(current_heading) # Восстанавливаем направление, которое было ПЕРЕД рисованием дочерних t.right(180) # Разворачиваемся на 180 градусов t.forward(branch_len) # Идем назад t.setheading(current_heading) # И снова восстанавливаем направление, которое было ДО этой ветки t.pendown() initial_branch_length = 100 initial_recursion_level = 9 # Глубина рекурсии (количество "поколений" веток) branch_angle = 28 # Угол расхождения основных веток # Устанавливаем начальный цвет пера для ствола tree_drawer.pencolor("saddlebrown") tree_drawer.pensize(12) draw_fractal_tree(initial_branch_length, tree_drawer, initial_recursion_level, branch_angle) screen.update() # Обновляем экран после отключения трассировки screen.mainloop()
initial_recursion_level
: Начальный уровень рекурсии. Чем он выше, тем пышнее и детализированнее будет дерево, но и рисоваться будет дольше (даже сtracer(0)
).- Изменение цвета и толщины: В коде есть примеры, как можно менять цвет и толщину веток в зависимости от уровня рекурсии, чтобы дерево выглядело более естественно.
- Случайность: Добавление небольшой случайности в углы (
angle_deviation
) и длины (length_modifier
) веток делает каждое дерево уникальным и более "живым". Попробуйте убрать эту случайность, чтобы увидеть "идеальное" симметричное дерево. - Третья ветка: Опционально добавлена логика для рисования третьей, более короткой центральной ветки для придания пышности.
- Возврат черепашки: Обратите особое внимание на блок кода в конце функции, который отвечает за возврат черепашки в исходное состояние. Это критически важная часть для правильной работы рекурсии при рисовании таких структур. Черепашка должна вернуться в точку начала той ветки, которую она только что нарисовала, и восстановить направление, которое у нее было до рисования этой ветки.
Мы создали сложную, детализированную структуру с помощью относительно небольшого и элегантного кода. Далее нас ждет еще один знаменитый фрактал – снежинка Коха.
Снежинка Коха: Геометрия самоподобия
Кривая Коха – это фрактал, который строится итеративно из простого отрезка прямой линии. Процесс построения одного "поколения" кривой Коха выглядит так:
- Берем отрезок прямой.
- Делим этот отрезок на три равные части.
- Среднюю часть удаляем.
- Вместо удаленной средней части достраиваем два таких же отрезка (по длине равных 1/3 исходного), которые образуют стороны равностороннего треугольника, "выпирающего" наружу.
- Теперь у нас получилось 4 отрезка вместо одного, каждый длиной в 1/3 от исходного.
- Далее этот же процесс (шаги 2-5) рекурсивно применяется к каждому из этих четырех новых отрезков.
Снежинка Коха (или звезда Коха) получается, если применить этот процесс построения кривой Коха к трем сторонам равностороннего треугольника.
Рекурсивная функция для кривой Коха
Нам понадобится рекурсивная функция koch_curve(t, order, size)
:
t
: наш объект черепашки.order
: "порядок" или глубина рекурсии. Это аналогlevel
из примера с деревом.size
: текущая длина отрезка, для которого мы строим кривую Коха.
Базовый случай: Если order
равен 0, это означает, что мы достигли нужной детализации, и черепашка просто рисует прямой отрезок длиной size
(t.forward(size)
).
Рекурсивный шаг (если order > 0
):
- Рекурсивно вызываем
koch_curve(t, order - 1, size / 3)
для первого из четырех сегментов. - Поворачиваем черепашку влево на 60 градусов (
t.left(60)
). - Рекурсивно вызываем
koch_curve(t, order - 1, size / 3)
для второго сегмента (первая сторона "шипа"). - Поворачиваем черепашку вправо на 120 градусов (
t.right(120)
). - Рекурсивно вызываем
koch_curve(t, order - 1, size / 3)
для третьего сегмента (вторая сторона "шипа"). - Поворачиваем черепашку влево на 60 градусов (
t.left(60)
). - Рекурсивно вызываем
koch_curve(t, order - 1, size / 3)
для четвертого сегмента.
Обратите внимание: в отличие от дерева, здесь черепашке не нужно специально "возвращаться" назад после каждого рекурсивного вызова. Она последовательно рисует все четыре части кривой, и в конце оказывается в той точке, где должна закончиться кривая Коха текущего порядка.
Собираем снежинку
Чтобы нарисовать снежинку Коха, мы просто трижды вызовем нашу функцию koch_curve
(для каждой стороны исходного равностороннего треугольника), поворачивая черепашку на 120 градусов вправо после каждой кривой.
import turtle # --- Настройки экрана и черепашки --- screen = turtle.Screen() screen.setup(width=800, height=700) screen.bgcolor("navy") turtle.colormode(255) screen.tracer(0) # Отключаем анимацию koch_artist = turtle.Turtle() koch_artist.hideturtle() koch_artist.speed("fastest") koch_artist.pencolor("white") # Белая снежинка на темном фоне koch_artist.pensize(1) # Тонкие линии для изящности # Начальная позиция для снежинки (подбирается экспериментально) koch_artist.penup() koch_artist.goto(-250, 150) koch_artist.pendown() # --- Рекурсивная функция для кривой Коха --- def koch_curve(t, order, size): """Рисует кривую Коха заданного порядка и размера.""" if order == 0: # Базовый случай t.forward(size) else: # Рекурсивный шаг: new_size = size / 3.0 # Важно делить на 3.0 для float деления koch_curve(t, order - 1, new_size) t.left(60) koch_curve(t, order - 1, new_size) t.right(120) koch_curve(t, order - 1, new_size) t.left(60) koch_curve(t, order - 1, new_size) # --- Функция для рисования полной снежинки Коха --- def draw_koch_snowflake(t, order, size): """Рисует снежинку Коха, состоящую из 3 кривых Коха.""" for _ in range(3): # Снежинка состоит из 3 кривых Коха koch_curve(t, order, size) t.right(120) # Поворот для следующей стороны исходного треугольника # --- Параметры и запуск --- fractal_order = 4 # Порядок фрактала (глубина рекурсии) # Попробуйте значения 0, 1, 2, 3, 4. # Выше 4-5 отрисовка может занять много времени и памяти. line_length = 450 # Длина стороны исходного "треугольника" снежинки draw_koch_snowflake(koch_artist, fractal_order, line_length) screen.update() # Обновляем экран screen.mainloop()
fractal_order
: Изменяйте этот параметр от 0 до 4. Вы увидите, как с каждым увеличением порядка снежинка становится все более детализированной и "пушистой". Это и есть суть фракталов – самоподобие на разных масштабах.order = 0
: Нарисуется простой равносторонний треугольник.order = 1
: Каждая сторона треугольника превратится в "шип".- И так далее...
line_length
: Длина стороны исходного треугольника. Влияет на общий размер снежинки.- Цвета и толщина: Попробуйте разные
pencolor
иpensize
.
Интересный факт: длина кривой Коха на каждой итерации увеличивается. Если исходный отрезок имел длину L, то после первой итерации общая длина 4-х сегментов будет 4 * (L/3) = (4/3)L. Это означает, что по мере увеличения порядка рекурсии до бесконечности, длина кривой Коха также стремится к бесконечности, хотя сама кривая остается в ограниченной области! 🤯
Снежинка Коха – еще один прекрасный пример того, как с помощью простого рекурсивного правила можно генерировать сложные структуры.
Заключение
Ну что ж, наше путешествие с черепашкой подошло к концу. Мы начали с самых азов: заставили нашу виртуальную черепашку двигаться, поворачиваться и оставлять первые следы на холсте. Затем мы освоили базовые фигуры, научились раскрашивать их и придавать им объем с помощью заливки.
Перейдя на новый уровень, мы узнали, как циклы позволят создавать правильные многоугольники и повторяющиеся элементы, следуя принципу DRY. Функции помогли нам структурировать код, сделав его более читаемым, гибким и переиспользуемым – мы научились создавать свои собственные "кисти" для рисования сложных объектов одной командой.
И, наконец, мы увидели, как функция, вызывающая саму себя, способна порождать бесконечно сложные и красивые фрактальные структуры, такие как ветвистые деревья и изящные снежинки Коха.
Главное, что дает Python Turtle, – это мгновенная визуальная отдача. Это хорошая "песочница" для экспериментов: изменили параметр, добавили новый цикл, попробовали другую логику – и сразу видите, как это отразилось на рисунке.
Возможности Turtle на этом не заканчиваются. Можно заставить черепашку реагировать на клики мыши или нажатия клавиш, создавая простые игры или интерактивные рисовальные программы. Так что не откладывайте Леонардо в долгий ящик!🐢