2025, Oct 30 21:46

Почему не видна форма turtle из изображения и как исправить в Tkinter

Почему спрайт turtle не появляется: в Tkinter PhotoImage забыли file=. Покажем исправление: загрузка изображения, register_shape и shape, варианты PNG/JPG.

Когда вы наследуете turtle.Turtle, чтобы рисовать собственный спрайт по изображению, легко споткнуться о тонкость API Tkinter. Симптом типичный: черепаха так и не появляется, хотя вы регистрируете форму и назначаете её экземпляру. Первопричина — неправильно использованный параметр в tkinter.PhotoImage.

Цель: черепаха с формой на основе изображения

Задача — создать класс, наследующийся от turtle.Turtle, и задать ему форму из файла изображения, например спрайт метеора или астероида.

Ошибочная реализация

Ниже — минимальный вариант, который на первый взгляд выглядит нормально, но ничего не выводит. Изображение не рендерится, и черепаха не появляется на холсте.

from turtle import Turtle, Screen, Shape
import tkinter
class Asteroid(Turtle):
    def __init__(self, hp):
        Turtle.__init__(self)
        self.hp = hp
        self.img_ref = tkinter.PhotoImage('Spinning-asteroid-6.gif')
        Screen().register_shape('meteor', Shape('image', self.img_ref))
        self.shape('meteor')

Что на самом деле не так

В tkinter.PhotoImage первый позиционный аргумент — это не имя файла. Он соответствует параметру master=, то есть родительскому виджету/владельцу. Чтобы загрузить с диска, путь нужно передать через ключ file=. Без file= изображение из вашего GIF вообще не загружается, поэтому у зарегистрированной формы нечего отображать, и черепаха остаётся невидимой.

Исправление

При создании PhotoImage используйте ключ file=. С этой единственной поправкой регистрация формы и её назначение начинают работать как задумано.

from turtle import Turtle, Screen, Shape
import tkinter
class Asteroid(Turtle):
    def __init__(self, hp):
        Turtle.__init__(self)
        self.hp = hp
        self.img_ref = tkinter.PhotoImage(file='Spinning-asteroid-6.gif')
        Screen().register_shape('meteor', Shape('image', self.img_ref))
        self.shape('meteor')

Использование разных форматов изображений

В актуальном tkinter можно использовать .png через tkinter.PhotoImage. Если ваш ресурс в PNG, загружайте его тем же способом с file= и регистрируйте как форму-изображение.

self.img_ref = tkinter.PhotoImage(file='my_image.png')
Screen().register_shape('meteor', Shape('image', self.img_ref))

С PIL можно также загружать .jpg, импортируя реализацию PhotoImage из PIL.ImageTk.

from PIL.ImageTk import PhotoImage as PILPhotoImage
self.img_ref = PILPhotoImage(file='my_image.jpg')
Screen().register_shape('meteor', Shape('image', self.img_ref))

Альтернативный подход к именам

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

Можно, например, вызвать register_shape('image.gif'), а затем self.shape('image.gif').

Наследование или композиция?

Хотя наследование от turtle.Turtle — популярный первый шаг, есть прагматичный взгляд, отдающий предпочтение композиции над наследованием в приложениях на turtle. Модуль turtle не создавался с прицелом на активное наследование, и смешивание логики игры прямо внутри библиотечного класса легко приводит к путанице. Это не влияет на проблему с изображением, описанную выше, но для поддерживаемости стоит помнить об этом.

Почему эта деталь важна

Неправильный параметр в PhotoImage тихо блокирует загрузку ресурса: с виду это будто проблема рендеринга или самой черепахи, а на деле — считывание файла. Понимание, что для путей на диске PhotoImage ждёт file=, экономит время и избавляет от поиска ошибок не в той части стека. Заодно это помогает без сюрпризов менять форматы (GIF, PNG, JPG через PIL).

Итоги

Если ваша черепаха с пользовательской формой не появляется, проверьте, как вы создаёте tkinter.PhotoImage. Всегда передавайте имя файла через file=. Затем зарегистрируйте изображение через Screen().register_shape и примените его методом .shape у экземпляра черепахи. Нужен PNG — грузите его через tkinter.PhotoImage; для JPEG используйте PhotoImage из PIL. А по мере роста проекта подумайте о том, чтобы оставлять turtle зависимостью, с которой вы работаете композиционно, а не базовым классом для наследования.

Статья основана на вопросе на StackOverflow от Aadvik и ответе от furas.