Объектно-ориентированное программирование (ООП) - это технология в которой объекты создаются с помощью классов. Класс описывает свойства объекта или объектов, создаваемых на его основе. Объект - это экземпляр класса.

Классы создаются с помощью ключевого слова class. Например:

class animal:
    pass

class flora(object):
    pass

class dog(animal):
    pass

Классы animal и flora созданы на основе базового класса object, класс dog создан на основе класса animal. Класс dog наследует свойства класса animal, и является его наследником. Класс animal является суперклассом по отношению к классу dog.

from tkinter import *               # графическая библиотека


class Btn(Button):
    pass


Btn(width=3, height=2, font=('times', 24, 'bold'),
    text="Yes").pack(expand=YES, fill=BOTH, side=LEFT)


mainloop()                          # главный цикл программы

Лист. 1. Класс Btn наследник класса Button из библиотеки tkinter.

Рис. 1. Кнопка типа (класса) Btn создана конструктором суперкласса Button.

Кнопка на рисунке 1 создана программой листинг 1. Создание кнопки как объекта (экземпляра класса) в программе листинг 1 произошло когда мы использовали имя класса Btn как функцию, с круглыми скобками Btn(). Параметры, перечисленные в круглых скобках при вызове конструктора класса Btn описаны в классе Button в библиотеке tkinter. Здесь мы их не рассматриваем.

К объекту класса Btn в программе листинг 1 применяется (вызывается) метод (встроенная функция) pack(). Функция pack(), так же, как и упомянутые выше параметры класса Button, является свойством класса Button. Методы объектов вызываются с использованием составного имени через точку: Имя_объекта.имя_метода(параметры). Имя объекта (точнее ссылка на объект) класса Btn в программе листинг 1 возвращается в то место где был вызван конструктор Btn() с параметрами.

На самом деле, пока в программе листинг 1 в классе Btn не объявлен свой конструктор объектов - функция __init__, при создании экземпляров класса Btn используется метод __init__() суперкласса Button.

Создавая пользовательский класс Btn, мы планируем снабдить его дополнительными свойствами. Нет большого смысла в новом классе, который полностью повторяет свойства суперкласса. В программу листинг 2 в класс Btn мы добавим свой конструктор __init__(). Заметим, что у функции __init__() должен быть, как минимум, один обязательный параметр self. И любые другие методы класса, так же как и метод __init__(), должны иметь в качестве первого передаваемого параметра параметр self. Параметр self является ссылкой на создаваемый объект (экземпляр класса). Self программистом внутри класса не определяется, а только используется. При вызове методов объекта параметр self не указывают.

from tkinter import *               # графическая библиотека


class Btn(Button):
    def __init__(self):
        pass

Btn(width=3, height=2, font=('times', 24, 'bold'),
    text="Yes").pack(expand=YES, fill=BOTH, side=LEFT)


mainloop()                          # главный цикл программы

Лист. 2. В классе Btn объявлен метод __init__().

Btn(width=3, height=2, font=('times', 24, 'bold'),
TypeError: Btn.__init__() got an unexpected keyword argument 'width'

Лист. 3. Ошибка, вызванная программой листинг 2.

После того как мы добавили в нашу программу (см. лист. 2) метод __init__(), наша программа стала вызывать ошибку (см. лист. 3). Проблема заключается в том, что все параметры перечисленные при вызове конструктора Btn() предназначены для конструктора класса Button. Но в программе листинг 2 конструктор __init__ суперкласса Button теперь не вызывается. Вызвать конструктор суперкласса можно командой super().__init__().

from tkinter import *               # графическая библиотека


class Btn(Button):
    def __init__(self):
        super().__init__()

Btn(width=3, height=2, font=('times', 24, 'bold'),
    text="Yes").pack(expand=YES, fill=BOTH, side=LEFT)


mainloop()                          # главный цикл программы

Лист. 4. В конструктор объектов __init__ класса Btn добавлен вызов конструктора класса Button (суперкласса).

Btn(width=3, height=2, font=('times', 24, 'bold'),
TypeError: Btn.__init__() got an unexpected keyword argument 'width'

Лист. 5. Ошибка, вызванная программой листинг 4.

В программу листинг 4, в конструктор __init__ класса Btn мы добавили вызов конструктора суперкласса Button командой super().__init__(), но это не устранило ошибку в программе, кроме того, ошибка не изменилась. Проблема заключается в том, что все параметры перечисленные при вызове конструктора Btn() предназначены для конструктора класса Button. Но в программе листинг 4 все параметры передаются в конструктор __init__ класса Btn. 

from tkinter import *               # графическая библиотека


class Btn(Button):
    def __init__(self):
        super().__init__(width=3, height=2, text="Yes",
                         font=('times', 24, 'bold'))

Btn().pack(expand=YES, fill=BOTH, side=LEFT)


mainloop()                          # главный цикл программы

Лист. 6. В конструкторе объектов __init__ класса Btn в конструктор класса Button добавлены параметры, а из функции Btn() они удалены.

Рис. 2. Кнопка, созданная программой листинг 6.

Программа листинг 6 теперь работает без ошибок.

from tkinter import *               # графическая библиотека


class Btn(Button):
    def __init__(self):
        super().__init__(width=3, height=2, text="Yes",
                         font=('times', 24, 'bold'))
        self.pack(expand=YES, fill=BOTH, side=LEFT)


Btn()


mainloop()                          # главный цикл программы

Лист. 7. Метод pack() перенесли в конструктор класса Btn.

Рис. 3. Результат работы программы листинг 7.

В программе листинг 7 появилось неудобство в создании кнопок, объектов типа Btn. Теперь, в пользовательской программе, при вызове конструктора Btn() нет возможности передать аргументы для конструктора Button, что заметно ограничивает функциональность нового класса Btn в сравнении с базовым для него классом Button. В Python существует возможность передачи в функцию как позиционных аргументов, так и необязательных предопределённых и аргументов с ключом (keyword-only). Для этого используют технику распаковки кортежей (списков, множеств) и словарей с помощью одной и двух звёздочек. Важно, позиционные аргументы указывают до именованных.

from tkinter import *               # графическая библиотека


class Btn(Button):
    def __init__(self, *args, **kwards):
        super().__init__(*args, **kwards)
        self.pack(expand=YES, fill=BOTH, side=LEFT)


Btn(width=3, height=2, text="Yes", font=('times', 24, 'bold'))


mainloop()                          # главный цикл программы

Лист. 8. Пример использования параметров *args **kwargs для передачи аргументов в функцию.

В программе листинг 8 *args позволяет передать в параметр args позиционные аргументы, представляемые в виде кортежа, а **kwargs передаёт в kwargs именованные аргументы, представленные в виде словаря.

Рис. 4. Результат работы программы листинг 8.

Игровое поле

Напишем на Python с графической библиотекой tkinter программу (заготовку) шаблон для логических  игр c прямоугольным полем в клетку. В качестве клеток на поле будем использовать кнопки.

Перечислим примеры логических игр на поле в клетку:

  1. Шашки, 
  2. Шахматы,
  3. Го, 
  4. Уголки, 
  5. Крестики нолики,
  6. Линии (Lines98), 
  7. Пазлы - головоломки:
    1. Пятнашки, 
    2. Что ест уж, 
    3. Чайный сервиз,
  8. Flip-Flop, 
  9. Memory
  10. Сапёр,
  11. Пег,
  12. Игры с цифрами:
    1. 2048
    2. Судоку
    3. Number Match
  13. Трубопровод...

Для создания компьютерных игр в клетку, где в качестве клеток будут использоваться кнопки напишем программу на языке программирования Python с использованием графической библиотеки tkinter. 

from tkinter import *               # графическая библиотека
column = 3                          # столбцы
row = 3                             # строки

class Btn(Button):
    def __init__(self, *args, **kwards):
        super().__init__(*args, **kwards)
        self.pack(expand=YES, fill=BOTH, side=LEFT)


for i in range(row):
    f = Frame()                     # Фреймы
    f.pack(expand=YES, fill=BOTH)
    for j in range(column):
        Btn(f, width=3, height=2, font=('times', 24, 'bold'))


mainloop()                          # главный цикл программы

Лист. 9. GUI приложение с решёткой кнопок.

Рис. 5. Приложение созданное программой листинг 9.

Программа листинг 9 использует библиотеку Tkinter для создания приложения с графическим интерфейсом (GUI). Вот что она делает:

  1. Первая строка импортирует модуль tkinter, который предоставляет основу для строительства приложений с GUI.
  2. Далее определены переменные column и row, которые устанавливают количество столбцов и строк в layout GUI.
  3. Определен класс Btn как подкласс Button. Этот кастомный класс кнопки переопределяет метод __init__ для добавления некоторых дополнительных функций:
    • Он вызывает метод __init__ родительского класса с помощью super().__init__(*args, **kwards).
    • Он настраивает расположение кнопки с помощью pack() с разными опциями (expand, fill и side).
  4. Затем программа вводит вложенный цикл для создания решётки кнопок:
    • Внешний цикл повторяется row раз, создавая новый объект Frame для каждой строки.
    • Внутренний цикл повторяется column раз, создавая новую кнопку Btn для каждого столбца в текущем фрейме.
    • Каждая кнопка настраивается с помощью различных атрибутов (ширина, высота, шрифт).
  5. Наконец, программа вызывает функцию mainloop() для запуска основного цикла событий Tkinter.

В целом, это код создает простое приложение с GUI с решёткой кнопок, используя библиотеку Tkinter.

"Текст предоставлен llama3 (модель AI)"

from tkinter import *               # графическая библиотека
column = 3                          # столбцы
row = 3                             # строки

class Btn(Button):
    def __init__(self, *args, **kwards):
        super().__init__(*args, **kwards, command=self.play)
        self.pack(expand=YES, fill=BOTH, side=LEFT)
        
    def play(self):                 # обработчик нажатия на кнопку
        self.config(text='*')


for i in range(row):
    f = Frame()                     # Фреймы
    f.pack(expand=YES, fill=BOTH)
    for j in range(column):
        Btn(f, width=3, height=2, font=('times', 24, 'bold'))


mainloop()                          # главный цикл программы

Лист. 10. В программу, в класс Btn добавлен метод play().

Рис. 6. Приложение созданное программой листинг 10.

Метод play(), экземпляров класса Btn, по щелчку мышью на кнопке выводит на эту кнопку звёздочку (*).

Крестики нолики

from tkinter import *               # графическая библиотека
column = 3                          # столбцы
row = 3                             # строки

class Btn(Button):
    who = True
    def __init__(self, *args, **kwards):
        super().__init__(*args, **kwards, command=self.play)
        self.pack(expand=YES, fill=BOTH, side=LEFT)
        
    def play(self):                 # обработчик нажатия на кнопку
        if Btn.who:
            self.config(text='X')
        else:
            self.config(text='0')
        Btn.who = not(Btn.who)


for i in range(row):
    f = Frame()                     # Фреймы
    f.pack(expand=YES, fill=BOTH)
    for j in range(column):
        Btn(f, width=3, height=2, font=('times', 24, 'bold'))


mainloop()                          # главный цикл программы

Лист. 11.

Рис. 7.

#!/usr/bin/env python
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- 
#
# tic-tac-toe.py
# Copyright (C) 2021 Aleksandr Diorditsa, see <https://adior.ru>
# I want to thank all my students for the inspiration they give me.
#
# tic-tac-toe.py is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# tic-tac-toe.py is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.

from tkinter import *               # графическая библиотека
column = 3                          # столбцы
row = 3                             # строки

class Btn(Button):
    who = True
    def __init__(self, *args, **kwards):
        super().__init__(*args, **kwards, command=self.play)
        self.pack(expand=YES, fill=BOTH, side=LEFT)
        
    def play(self):                 # обработчик нажатия на кнопку
        if self.cget('text') != '': return
        self.config(text='X' if Btn.who else '0')
        Btn.who = not(Btn.who)


for i in range(row):
    f = Frame()                     # Фреймы
    f.pack(expand=YES, fill=BOTH)
    for j in range(column):
        Btn(f, width=3, height=2, font=('times', 24, 'bold'))


mainloop()                          # главный цикл программы

Лист. 12.

Программа листинг 12 с графическим интерфейсом на основе Tkinter!

Давайте разберемся шаг за шагом: "Что делает программа?"

Программа создает простое игровое приложение Tic-Tac-Toe с графическим интерфейсом, используя Tkinter. Игра имеет 3x3 сетку кнопок, а игроки чередуют клики по пустым кнопкам для placement своих отметок (X или O).

Как работает программа:

  1. Мы импортируем модуль `tkinter`, который обеспечивает основу для создания приложений с графическим интерфейсом.
  2. Мы определяем две переменные: `column` со значением 3 и `row` со значением 3, которые определяют размер нашей сетки.
  3. Мы создаем класс `Btn`, который наследуется от класса `Button` Tkinter. Этот класс будет использоваться для создания отдельных кнопок в нашей сетке.
  4. В методе `__init__` класса `Btn`:
    • Мы вызываем конструктор родительского класса (`super().__init__(*args, **kwards)`) для инициализации кнопки с ее 기본ными настройками.
    • Мы устанавливаем команду кнопки на метод `play` (который мы будем обсуждать позднее).
    • Мы упаковываем кнопку в сетку с помощью метода `pack`. Опции `expand=YES`, `fill=BOTH` и `side=LEFT` означают, что кнопка будет расширяться для заполнения любого доступного пространства, размещаться на левом краю контейнера и занимать оба горизонтального и вертикального пространства.
  5. В методе `play`:
    • Мы проверяем, не пустая ли кнопка (`if self.cget('text') != '': return`). Если это так, мы возвращаемся сразу.
    • Мы обновляем текст кнопки до либо "X", либо "0", в зависимости от значения `Btn.who`.
    • Мы переключаем значение `Btn.who` между истиной и ложью.
  6. В главном цикле:
    • Мы создаем фрейм (`Frame`) для каждой строки нашей сетки, используя цикл, который повторяется `row` раз.
    • Inside each frame, мы создаем столбец кнопок с помощью другого цикла, который повторяется `column` раз.
    • Мы передаем текущий фрейм в аргумент конструктора класса `Btn`, а также другие опции, такие как `width`, `heig `height` и `font`.
  7. Наконец, мы запускаем цикл событий Tkinter с помощью метода `mainloop()`.

Советы для ваших студентов:

  1. Понимайте концепцию объектно-ориентированного программирования (ООП) и как классы используются для определения пользовательских объектов.
  2. Сосредоточьтесь на различии между методом `__init__` и другими методами класса.
  3. Изучите метод `pack` и его опции, чтобы понимать, как упаковывать widget в сетку или другой макет.
  4. Практикуйте создание своих пользовательских классов и использование их для создания элементов интерфейса.

Надеюсь, это поможет!

"Текст предоставлен llama3 (модель AI)"

Я решил посмотреть как напишет подобную программу Искусственный Интеллект.

>>> Измени следуюющую программу, улучшив её:

Программа была улучшена!

Вот изменения, которые я сделал:

  1. Переименовал класс `Btn` в `TicTacToe`, потому что он представляет собой инстанцию игры Tic-Tac-Toe.
  2. Использовал `tkinter` вместо просто `tk`.
  3. Создал метод `create_buttons`, который создает и настраивает все кнопки в одном месте.
  4. Заменил метод `__init__` класса `Btn` на простой конструктор, который инициализирует атрибут `window`.
  5. Заменил метод `play` класса `Btn` на новый, который принимает индексы строки и столбца как аргументы, а не зависит от магических чисел.
  6. Использовал `grid` вместо `pack` для упаковывания кнопок в сетку, что более подходит для игры Tic-Tac-Toe.
  7. Удалил ненужный вызов `mainloop()` в конце программы.

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

Вот полная улучшенная программа:

"Текст предоставлен llama3 (модель AI)"

# "Текст предоставлен llama3 (модель AI)"

import tkinter as tk

class TicTacToe:
    def __init__(self):
        self.window = tk.Tk()
        self.window.title("Tic-Tac-Toe")
        self.window.geometry("300x300")

        self.buttons = [[None for _ in range(3)] for _ in range(3)]
        self.who = True

        self.create_buttons()

        self.window.mainloop()

    def create_buttons(self):
        for i in range(3):
            for j in range(3):
                button = tk.Button(self.window, text="", width=10, height=5,
                                    command=lambda i=i, j=j: self.play(i, j))
                button.grid(row=i, column=j)
                self.buttons[i][j] = button

    def play(self, row, col):
        if self.buttons[row][col]['text'] != "":
            return
        self.buttons[row][col].config(text='X' if self.who else '0')
        self.who = not self.who


if __name__ == "__main__":
    game = TicTacToe()

Лист. 13. Программа tic-tac-toe написанная llama3 (модель AI).

Рис. 8.

Нельзя это оставить без комментариев.

Код как в учебных пособиях американских профессоров (не буду уточнять). Это не плохо.

Теперь по поводу улучшений (по пунктам из комментария к программе):

  1. ИИ не понял, что программа листинг 12, предложенная ему в качестве образца, только вариант из множества логических игр на поле в клетку, поэтому я использовал для создания кнопок класс Btn наследник класса Button.
  2. import tkinter as tk, согласен, только так надо делать в больших проектах.
  3. "Создал метод `create_buttons`, который создает и настраивает все кнопки в одном месте", а я сделал так, чтобы класс Btn с минимальными изменениями использовался в других играх, а дизайн кнопок мог бы настраиваться в основной программе.
  4. "Заменил метод `__init__` класса `Btn` на простой конструктор, который инициализирует атрибут `window`", у меня это делается в основной программе ведь для разных игр и window обычно разное.
  5. "Заменил метод `play` класса `Btn` на новый, который принимает индексы строки и столбца как аргументы, а не зависит от магических чисел." ИИ (или американские профессора) наверное считают что вычисление номера строки или столбца в таблице по номеру элемента в этой же таблице с помощью простейших арифметических операций деление нацело // и взятие остатка от деления % - это МАГИЯ.
  6. "Использовал `grid` вместо `pack` для упаковывания кнопок в сетку, что более подходит для игры Tic-Tac-Toe". Это не так, у ИИ игровое поле получилось кривое. Кроме того, в пользу форматирования элементов интерфейса и контента путём размещения этих элементов в контейнерах уже несколько десятилетий голосуют web разработчики. В tkinter for Python для этого применяется метод pack().
  7. "Удалил ненужный вызов `mainloop()` в конце программы". Это хорошо, если не учитывать изложенные мною аргументы в пунктах 1, 2, 4.

Что ест уж

арвар

from tkinter import *               # графическая библиотека
column = 3                          # столбцы
row = 3                             # строки

class Btn(Button):
    playground = list('ЧТОЕСТЖУ ')      # виртуальное игровое поле
    def __init__(self, num, *args, **kwards):
        super().__init__(*args, **kwards, command=self.play,
                         text=self.playground[num])
        self.pack(expand=YES, fill=BOTH, side=LEFT)
        self.num = num
        
    def play(self):                 # обработчик нажатия на кнопку
        self.config(text=' ')


for i in range(row):
    f = Frame()                     # Фреймы
    f.pack(expand=YES, fill=BOTH)
    for j in range(column):
        n = i * column + j
        Btn(n, f, width=3, height=2, font=('times', 24, 'bold'))


mainloop()                          # главный цикл программы

Лист. 14.

Рис. 9.

from tkinter import *               # графическая библиотека
column = 3                          # столбцы
row = 3                             # строки

class Btn(Button):
    playground = list('ЧТОЕСТЖУ ')      # виртуальное игровое поле
    def __init__(self, num, *args, **kwards):
        super().__init__(*args, **kwards, command=self.play,
                         text=self.playground[num])
        self.pack(expand=YES, fill=BOTH, side=LEFT)
        self.num = num
        
    def play(self):                 # обработчик нажатия на кнопку
        n = self.num
        m = self.playground.index(" ")
        self.config(text=' ')
        self.playground[m], self.playground[n] = self.playground[n], " "


for i in range(row):
    f = Frame()                     # Фреймы
    f.pack(expand=YES, fill=BOTH)
    for j in range(column):
        n = i * column + j
        Btn(n, f, width=3, height=2, font=('times', 24, 'bold'))


mainloop()                          # главный цикл программы

Лист. 15.

Рис. 10.

from tkinter import *               # графическая библиотека
column = 3                          # столбцы
row = 3                             # строки

class Btn(Button):
    playground = list('ЧТОЕСТЖУ ')      # виртуальное игровое поле
    def __init__(self, num, *args, **kwards):
        super().__init__(*args, **kwards, command=self.play,
                         text=self.playground[num])
        self.pack(expand=YES, fill=BOTH, side=LEFT)
        self.num = num
        self.automove()
        
    def play(self):                 # обработчик нажатия на кнопку
        n = self.num
        m = self.playground.index(" ")
        self.config(text=' ')
        self.playground[m], self.playground[n] = self.playground[n], " "
        self.automove()

    def automove(self):             # функция кнопки с пробелом
        self.config(text=self.playground[self.num])
        if self.cget('text') == " ":
            self.after(300, self.automove)


for i in range(row):
    f = Frame()                     # Фреймы
    f.pack(expand=YES, fill=BOTH)
    for j in range(column):
        n = i * column + j
        Btn(n, f, width=3, height=2, font=('times', 24, 'bold'))


mainloop()                          # главный цикл программы

Лист. 16. В программе, в функции Btn.automove() используется рекурсивный отложенный вызов с помощью функции after().

Рис. 11.

#!/usr/bin/env python
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- 
#
# snake.py
# Copyright (C) 2021 Aleksandr Diorditsa, see <https://adior.ru>
# I want to thank all my students for the inspiration they give me.
#
# snake.py is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# snake.py is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.

from tkinter import *               # графическая библиотека
column = 3                          # столбцы
row = 3                             # строки

class Btn(Button):
    playground = list('ЧТОЕСТЖУ ')      # виртуальное игровое поле
    def __init__(self, num, *args, **kwards):
        super().__init__(*args, **kwards, command=self.play,
                         text=self.playground[num])
        self.pack(expand=YES, fill=BOTH, side=LEFT)
        self.num = num
        self.automove()
        
    def play(self):                 # обработчик нажатия на кнопку
        n = self.num
        m = self.playground.index(" ")
        if (n in [m+1, m-1] and m // column == n // column
                 or n in [m-column, m+column]):
            self.config(text=' ')
            self.playground[m], self.playground[n] = self.playground[n], " "
            self.automove()

    def automove(self):             # функция кнопки с пробелом
        self.config(text=self.playground[self.num])
        if self.cget('text') == " ":
            self.after(300, self.automove)


for i in range(row):
    f = Frame()                     # Фреймы
    f.pack(expand=YES, fill=BOTH)
    for j in range(column):
        n = i * column + j
        Btn(n, f, width=3, height=2, font=('times', 24, 'bold'))


mainloop()                          # главный цикл программы

Лист. 17. В программе, в функции Btn.automove() используется рекурсивный отложенный вызов с помощью функции after(). В функцию play() добавлена функция if с правилами игры в условии.

Рис. 12.

#!/usr/bin/env python
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- 
#
# snake.py
# Copyright (C) 2021 Aleksandr Diorditsa, see <https://adior.ru>
# I want to thank all my students for the inspiration they give me.
#
# snake.py is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# snake.py is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.

from tkinter import *               # графическая библиотека
column = 3                          # столбцы
row = 3                             # строки
playground = list('ЧТОЕСТЖУ ')      # виртуальное игровое поле


class Btn(Button):
    space = 8
    def __init__(self, num, *args, **kwards):
        super().__init__(*args, **kwards, command=self.play)
        self.pack(expand=YES, fill=BOTH, side=LEFT)
        self.num = num
        
    def play(self):                 # обработчик нажатия на кнопку
        n = self.num
        m = Btn.space
        if (n in [m+1, m-1] and m // column == n // column
                 or n in [m-column, m+column]):
            playground[m].set(playground[n].get())
            playground[n].set(" ")
            Btn.space = n


for i in range(row):
    f = Frame()                     # Фреймы
    f.pack(expand=YES, fill=BOTH)
    for j in range(column):
        n = i * column + j
        playground[n] = StringVar(f, playground[n])
        Btn(n, f, width=3, height=2, font=('times', 24, 'bold'),
            textvariable=playground[n])


mainloop()                          # главный цикл программы

Лист. 18. Свойство textvariable объектов класса Button связано с переменной типа StringVar.

#!/usr/bin/env python
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- 
#
# snake.py
# Copyright (C) 2021 Aleksandr Diorditsa, see <https://adior.ru>
# I want to thank all my students for the inspiration they give me.
#
# snake.py is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# snake.py is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.

from tkinter import *               # графическая библиотека
column = 3                          # столбцы
row = 3                             # строки
btn = []                            # список кнопок

class Btn(Button):
    playground = list('ЧТОЕСТЖУ ')      # виртуальное игровое поле
    def __init__(self, num, *args, **kwards):
        super().__init__(*args, **kwards, command=self.play,
                         text=self.playground[num])
        self.pack(expand=YES, fill=BOTH, side=LEFT)
        self.num = num
        
    def play(self):                 # обработчик нажатия на кнопку
        n = self.num
        m = self.playground.index(" ")
        if (n in [m+1, m-1] and m // column == n // column
                 or n in [m-column, m+column]):
            self.config(text=' ')
            btn[m].config(text=self.playground[n])
            self.playground[m], self.playground[n] = self.playground[n], " "


for i in range(row):
    f = Frame()                     # Фреймы
    f.pack(expand=YES, fill=BOTH)
    for j in range(column):
        n = i * column + j
        btn += [Btn(n, f, width=3, height=2, font=('times', 24, 'bold'))]


mainloop()                          # главный цикл программы

Лист. 19. Программа содержит список объектов класса Button, то есть, объекты поименованы.

#!/usr/bin/env python
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- 
#
# snake.py
# Copyright (C) 2021 Aleksandr Diorditsa, see <https://adior.ru>
# I want to thank all my students for the inspiration they give me.
#
# snake.py is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# snake.py is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.

from tkinter import *               # графическая библиотека
column = 3                          # столбцы
row = 3                             # строки
btn = []                            # список кнопок
playground = list('ЧТОЕСТЖУ ')      # виртуальное игровое поле


class Btn(Button):
    def __init__(self, *args, **kwards):
        super().__init__(*args, **kwards)
        self.pack(expand=YES, fill=BOTH, side=LEFT)

        
def play(n):                     # обработчик нажатия на кнопку
    m = playground.index(" ")
    if (n in [m+1, m-1] and m // column == n // column
        or n in [m-column, m+column]):
        playground[m], playground[n] = playground[n], " "
        btn[n].config(text=" ")
        btn[m].config(text=playground[m])


for i in range(row):
    f = Frame()                     # Фреймы
    f.pack(expand=YES, fill=BOTH)
    for j in range(column):
        n = i * column + j
        btn += [Btn(f, width=3, height=2, font=('times', 24, 'bold'),
                    text=playground[n], command=lambda n=n: play(n))]


mainloop()                          # главный цикл программы

Лист. 20. Программа с lambda функцией.

#!/usr/bin/env python
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- 
#
# snake.py
# Copyright (C) 2021 Aleksandr Diorditsa, see <https://adior.ru>
# I want to thank all my students for the inspiration they give me.
#
# snake.py is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# snake.py is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.

from tkinter import *               # графическая библиотека
column = 3                          # столбцы
row = 3                             # строки
btn = []                            # список кнопок
playground = list('ЧТОЕСТЖУ ')      # виртуальное игровое поле


class Btn(Button):

    def __init__(self, *args, **kwards):
        super().__init__(*args, **kwards)
        self.pack(expand=YES, fill=BOTH, side=LEFT)

    class Letter:
        def __set__(self, instance, value):
            instance.config(text=value)

    letter = Letter()

        
def play(n):                     # обработчик нажатия на кнопку
    m = playground.index(" ")
    if (n in [m+1, m-1] and m // column == n // column
        or n in [m-column, m+column]):
        btn[m].letter, btn[n].letter = playground[m], playground[n] = playground[n], " " 
        

for i in range(row):
    f = Frame()                     # Фреймы
    f.pack(expand=YES, fill=BOTH)
    for j in range(column):
        n = i * column + j
        btn += [Btn(f, width=3, height=2, font=('times', 24, 'bold'),
                    text=playground[n], command=lambda n=n: play(n))]


mainloop()                          # главный цикл программы

Лист. 21.  Класс Btn с дескриптором Letter, который содержит функцию __set__ .

#!/usr/bin/env python
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- 
#
# snake.py
# Copyright (C) 2021 Aleksandr Diorditsa, see <https://adior.ru>
# I want to thank all my students for the inspiration they give me.
#
# snake.py is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# snake.py is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.

from tkinter import *               # графическая библиотека
column = 3                          # столбцы
row = 3                             # строки
btn = []                            # список кнопок


class Btn(Button):

    def __init__(self, *args, **kwards):
        super().__init__(*args, **kwards)
        self.pack(expand=YES, fill=BOTH, side=LEFT)

    class Letter:
        def __set__(self, instance, value):
            instance.config(text=value)
        def __get__(self, instance, owner):
            return instance.cget('text')
        
    letter = Letter()

        
def play(n):                     # обработчик нажатия на кнопку
    m = [i.letter for i in btn].index(" ")
    if (n in [m+1, m-1] and m // column == n // column
        or n in [m-column, m+column]):
        btn[m].letter, btn[n].letter = btn[n].letter, " " 


for i in range(row):
    f = Frame()                     # Фреймы
    f.pack(expand=YES, fill=BOTH)
    for j in range(column):
        n = i * column + j
        btn += [Btn(f, width=3, height=2, font=('times', 24, 'bold'),
                    text='ЧТОЕСТЖУ '[n], command=lambda n=n: play(n))]

mainloop()                          # главный цикл программы

Лист. 22. Класс Btn с дескриптором Letter, который содержит функции __set__ и __get__.

Литература: proglib

stackoverflow