Интерфейс python: Графический интерфейс на Python за 5 минут / Блог компании Edison / Хабр

Содержание

Графический интерфейс на Python за 5 минут / Блог компании Edison / Хабр

Python легко использовать. В нем вы можете найти огромное количество библиотек для чего угодно. И это его основное преимущество. Из нескольких строк кода вы ничего не сделаете. Если вам нужны скрипты для личного пользования или для технически подкованной аудитории, то вам даже не придется думать о графическом интерфейсе.

Однако иногда ваша целевая аудитория не сильно подкована технически. Люди не против использовать ваши скрипты на Python до тех пор пока им не нужно смотреть на одну строку кода. В таком случае скриптов командной строки будет недостаточно. В идеале вам нужен графический интерфейс. Цель этого поста использовать только Python.

Библиотеки Python, которые можно использовать для графического интерфейса

По сути, есть 3 большие библиотеки Python для графического интерфейса; Tkinter, wxPython и PyQT. Рассматривая их, я не нашел там ничего из того, что мне нравится в Python. Библиотеки Python, как правило, очень хорошо абстрагируются от супер-технических моментов. Если бы мне нужно было работать с объектно-ориентированным программированием, я мог бы с таким же успехом загрузить Java или .Net.



Статья переведена при поддержке компании EDISON Software, которая заботится о здоровье программистов и их завтраке, а также разрабатывает программное обеспечение на заказ.

Однако, к счастью, я наткнулся на четвёртый вариант, который был мне по душе. Это PySimpleGUI, я до сих пор ей пользуюсь. Как ни странно, эта библиотека использует все 3 популярные библиотеки, о которых шла речь выше, но при этом абстрагируется от супер технических моментов

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

Проверьте два одинаковых файла

Я рассказал как это сделать в своей статье “3 быстрых способа сравнить данные в Python”. Мы можем использовать первый раздел, проверку целостности данных, чтобы попытаться создать пользовательский интерфейс.
По факту нам нужно загрузить два файла и выбрать шифрование, которое мы хотели бы использовать для сравнения файлов.

Запрограммируйте графический интерфейс

Чтобы создать графический интерфейс, можно использовать этот код:

import PySimpleGUI as sg
layout = [
    [sg.Text('File 1'), sg.InputText(), sg.FileBrowse(),
     sg.Checkbox('MD5'), sg.Checkbox('SHA1')
     ],
    [sg.Text('File 2'), sg.InputText(), sg.FileBrowse(),
     sg.Checkbox('SHA256')
     ],
    [sg.Output(size=(88, 20))],
    [sg.Submit(), sg.Cancel()]
]
window = sg.Window('File Compare', layout)
while True:                             # The Event Loop
    event, values = window.read()
    # print(event, values) #debug
    if event in (None, 'Exit', 'Cancel'):
        break

в результате мы получим:

Подключаем логику

Когда есть пользовательский интерфейс, легко понять, как подключить остальную часть кода. Нам просто нужно следить за тем, что вводит пользователь и действовать соответственно. Мы можем очень легко сделать это с помощью следующего кода:

import PySimpleGUI as sg
import re
import hashlib
def hash(fname, algo):
    if algo == 'MD5':
        hash = hashlib.md5()
    elif algo == 'SHA1':
        hash = hashlib.sha1()
    elif algo == 'SHA256':
        hash = hashlib.sha256()
    with open(fname) as handle: #opening the file one line at a time for memory considerations
        for line in handle:
            hash.update(line.encode(encoding = 'utf-8'))
    return(hash.hexdigest())
layout = [
    [sg.Text('File 1'), sg.InputText(), sg.FileBrowse(),
     sg.Checkbox('MD5'), sg.Checkbox('SHA1')
     ],
    [sg.Text('File 2'), sg.InputText(), sg.FileBrowse(),
     sg.Checkbox('SHA256')
     ],
    [sg.Output(size=(88, 20))],
    [sg.Submit(), sg.Cancel()]
]
window = sg.Window('File Compare', layout)
while True:                             # The Event Loop
    event, values = window.read()
    # print(event, values) #debug
    if event in (None, 'Exit', 'Cancel'):
        break
    if event == 'Submit':
        file1 = file2 = isitago = None
        # print(values[0],values[3])
        if values[0] and values[3]:
            file1 = re. findall('.+:\/.+\.+.', values[0])
            file2 = re.findall('.+:\/.+\.+.', values[3])
            isitago = 1
            if not file1 and file1 is not None:
                print('Error: File 1 path not valid.')
                isitago = 0
            elif not file2 and file2 is not None:
                print('Error: File 2 path not valid.')
                isitago = 0
            elif values[1] is not True and values[2] is not True and values[4] is not True:
                print('Error: Choose at least one type of Encryption Algorithm')
            elif isitago == 1:
                print('Info: Filepaths correctly defined.')
                algos = [] #algos to compare
                if values[1] == True: algos.append('MD5')
                if values[2] == True: algos.append('SHA1')
                if values[4] == True: algos.append('SHA256')
                filepaths = [] #files
                filepaths.append(values[0])
                filepaths.append(values[3])
                print('Info: File Comparison using:', algos)
                for algo in algos:
                    print(algo, ':')
                    print(filepaths[0], ':', hash(filepaths[0], algo))
                    print(filepaths[1], ':', hash(filepaths[1], algo))
                    if hash(filepaths[0],algo) == hash(filepaths[1],algo):
                        print('Files match for ', algo)
                    else:
                        print('Files do NOT match for ', algo)
        else:
            print('Please choose 2 files. ')
window.close()

Он даст нам такой результат:

Заключительные мысли

Может это и не самый красивый пользовательский интерфейс, но PySimpleGUI позволяет вам быстро разворачивать простые пользовательские интерфейсы Python и делиться ими с кем угодно. Код, который вам нужен для этого, прост и легко читается. У вас все еще будет проблема запуска кода для получения пользовательского интерфейса. Из-за этого могут возникнуть сложности с совместным использованием кода. Советую скачать что-то вроде PyInstaller, который превратит ваш скрипт на python в .exe файл. Люди смогут запустить его просто нажав на него дважды.

Создание графического интерфейса на Python 3 с Tkinter ~ PythonRu

В этом уроке мы узнаем, как разрабатывать графические пользовательские интерфейсы, с помощью разбора некоторых примеров графического интерфейса Python с использованием библиотеки Tkinter.

Библиотека Tkinter установлена в Python в качестве стандартного модуля, поэтому нам не нужно устанавливать что-либо для его использования. Tkinter — очень мощная библиотека. Если вы уже установили Python, можете использовать IDLE, который является интегрированной IDE, поставляемой в Python, эта IDE написана с использованием Tkinter. Звучит круто!

Мы будем использовать Python 3.7 поэтому, если вы все еще используете Python 2.x, настоятельно рекомендуем перейти на Python 3.x, если вы не в курсе нюансов изменения языка, с целью, чтобы вы могли настроить код для запуска без ошибок.

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

Создание своего первого графического интерфейса

Для начала, следует импортировать Tkinter и создать окно, в котором мы зададим его название:

from tkinter import *


window = Tk()
window. title("Добро пожаловать в приложение PythonRu")
window.mainloop()

Результат будет выглядеть следующим образом:

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

В случае, если вы забудете вызвать функцию mainloop , для пользователя ничего не отобразится.

Создание виджета Label

Чтобы добавить текст в наш предыдущий пример, мы создадим lbl , с помощью класса Label, например:

lbl = Label(window, text="Привет")

Затем мы установим позицию в окне с помощью функции grid и укажем ее следующим образом:

lbl.grid(column=0, row=0)

Полный код, будет выглядеть следующим образом:

from tkinter import *  
  
  
window = Tk()  
window. title("Добро пожаловать в приложение PythonRu")  
lbl = Label(window, text="Привет")  
lbl.grid(column=0, row=0)  
window.mainloop()

И вот как будет выглядеть результат:

Если функция grid не будет вызвана, текст не будет отображаться.

Настройка размера и шрифта текста

Вы можете задать шрифт текста и размер. Также можно изменить стиль шрифта. Для этого передайте параметр font таким образом:

lbl = Label(window, text="Привет", font=("Arial Bold", 50))

Обратите внимание, что параметр font может быть передан любому виджету, для того, чтобы поменять его шрифт, он применяется не только к Label.

Отлично, но стандартное окно слишком мало. Как насчет настройки размера окна?

Настройка размеров окна приложения

Мы можем установить размер окна по умолчанию, используя функцию geometry следующим образом:

window. geometry('400x250')

В приведенной выше строке устанавливается окно шириной до 400 пикселей и высотой до 250 пикселей.

Попробуем добавить больше виджетов GUI, например, кнопки и посмотреть, как обрабатывается нажатие кнопок.

Добавление виджета Button

Начнем с добавления кнопки в окно. Кнопка создается и добавляется в окно так же, как и метка:

btn = Button(window, text="Не нажимать!")
btn.grid(column=1, row=0)

Наш код будет выглядеть вот так:

from tkinter import *  
  

window = Tk()  
window.title("Добро пожаловать в приложение PythonRu")  
window.geometry('400x250')  
lbl = Label(window, text="Привет", font=("Arial Bold", 50))  
lbl.grid(column=0, row=0)  
btn = Button(window, text="Не нажимать!")  
btn.grid(column=1, row=0)  
window.mainloop()

Результат будет следующим:

Обратите внимание, что мы помещаем кнопку во второй столбец окна, что равно 1. Если вы забудете и поместите кнопку в том же столбце, который равен 0, он покажет только кнопку.

Изменение цвета текста и фона у Button

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

btn = Button(window, text="Не нажимать!", bg="black", fg="red")

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

Кнопка Click

Для начала, мы запишем функцию, которую нужно выполнить при нажатии кнопки:

def clicked():
    lbl.configure(text="Я же просил...")

Затем мы подключим ее с помощью кнопки, указав следующую ​​функцию:

btn = Button(window, text="Не нажимать!", command=clicked)

Обратите внимание: мы пишем clicked, а не clicked()с круглыми скобками. Теперь полный код будет выглядеть так:

from tkinter import *  
  
  
def clicked():  
    lbl.configure(text="Я же просил...")  
  
  
window = Tk()  
window.title("Добро пожаловать в приложение PythonRu")  
window.geometry('400x250')  
lbl = Label(window, text="Привет", font=("Arial Bold", 50))  
lbl.grid(column=0, row=0)  
btn = Button(window, text="Не нажимать!", command=clicked)  
btn.grid(column=1, row=0)  
window.mainloop()

При нажатии на кнопку, результат, как и ожидалось, будет выглядеть следующим образом:

Круто!

Получение ввода с использованием класса Entry (текстовое поле Tkinter)

В предыдущих примерах GUI Python мы ознакомились со способами добавления простых виджетов, а теперь попробуем получить пользовательский ввод, используя класс Tkinter Entry (текстовое поле Tkinter).
Вы можете создать текстовое поле с помощью класса Tkinter Entry следующим образом:

txt = Entry(window, width=10)

Затем вы можете добавить его в окно, используя функцию grid.
Наше окно будет выглядеть так:

from tkinter import *  
  
  
def clicked():  
    lbl.configure(text="Я же просил...")  
  
  
window = Tk()  
window.title("Добро пожаловать в приложение PythonRu")  
window.geometry('400x250')  
lbl = Label(window, text="Привет")  
lbl.grid(column=0, row=0)  
txt = Entry(window,width=10)  
txt.grid(column=1, row=0)  
btn = Button(window, text="Не нажимать!", command=clicked)  
btn.grid(column=2, row=0)  
window.mainloop()

Полученный результат будет выглядеть так:

Теперь, если вы нажмете кнопку, она покажет то же самое старое сообщение, но что же будет с отображением введенного текста в виджет Entry?

Во-первых, вы можете получить текст ввода, используя функцию get. Мы можем записать код для выбранной функции таким образом:

def clicked():
    res = "Привет {}".format(txt.get())
    lbl. configure(text=res)

Если вы нажмете на кнопку — появится текст «Привет » вместе с введенным текстом в виджете записи. Вот полный код:

from tkinter import *  
  
  
def clicked():  
    res = "Привет {}".format(txt.get())  
    lbl.configure(text=res)  
  
  
window = Tk()  
window.title("Добро пожаловать в приложение PythonRu")  
window.geometry('400x250')  
lbl = Label(window, text="Привет")  
lbl.grid(column=0, row=0)  
txt = Entry(window,width=10)  
txt.grid(column=1, row=0)  
btn = Button(window, text="Клик!", command=clicked)  
btn.grid(column=2, row=0)  
window.mainloop()

Запустите вышеуказанный код и проверьте результат:

Прекрасно!

Каждый раз, когда мы запускаем код, нам нужно нажать на виджет ввода, чтобы настроить фокус на ввод текста, но как насчет автоматической настройки фокуса?

Установка фокуса виджета ввода

Здесь все очень просто, ведь все, что нам нужно сделать, — это вызвать функцию focus:

txt. focus()

Когда вы запустите свой код, вы заметите, что виджет ввода в фокусе, который дает возможность сразу написать текст.

Отключить виджет ввода

Чтобы отключить виджет ввода, отключите свойство состояния:

txt = Entry(window,width=10, state='disabled')

Теперь вы не сможете ввести какой-либо текст.

Добавление виджета Combobox

Чтобы добавить виджет поля с выпадающем списком, используйте класс Combobox из ttk следующим образом:

from tkinter.ttk import Combobox


combo = Combobox(window)

Затем добавьте свои значения в поле со списком.

from tkinter import *  
from tkinter.ttk import Combobox  
  
  
window = Tk()  
window.title("Добро пожаловать в приложение PythonRu")  
window.geometry('400x250')  
combo = Combobox(window)  
combo['values'] = (1, 2, 3, 4, 5, "Текст")  
combo. current(1)  
combo.grid(column=0, row=0)  
window.mainloop()

Как видите с примера, мы добавляем элементы combobox, используя значения tuple.
Чтобы установить выбранный элемент, вы можете передать индекс нужного элемента текущей функции.
Чтобы получить элемент select, вы можете использовать функцию get вот таким образом:

combo.get()

Добавление виджета Checkbutton (чекбокса)

С целью создания виджета checkbutton, используйте класс Checkbutton:

from tkinter.ttk import Checkbutton


chk = Checkbutton(window, text='Выбрать')

Кроме того, вы можете задать значение по умолчанию, передав его в параметр var в Checkbutton:

from tkinter import *  
from tkinter.ttk import Checkbutton  
  
  
window = Tk()  
window.title("Добро пожаловать в приложение PythonRu")  
window. geometry('400x250')  
chk_state = BooleanVar()  
chk_state.set(True)  
chk = Checkbutton(window, text='Выбрать', var=chk_state)  
chk.grid(column=0, row=0)  
window.mainloop()

Посмотрите на результат:

Установка состояния Checkbutton

Здесь мы создаем переменную типа BooleanVar, которая не является стандартной переменной Python, это переменная Tkinter, затем передаем ее классу Checkbutton, чтобы установить состояние чекбокса как True в приведенном выше примере.

Вы можете установить для BooleanVar значение false, что бы чекбокс не был отмечен.
Так же, используйте IntVar вместо BooleanVar и установите значения 0 и 1.

chk_state = IntVar()
chk_state.set(0) 
chk_state.set(1) 

Эти примеры дают тот же результат, что и BooleanVar.

Добавление виджетов Radio Button

Чтобы добавить radio кнопки, используйте класс RadioButton:

rad1 = Radiobutton(window,text='Первый', value=1)

Обратите внимание, что вы должны установить value для каждой radio кнопки с уникальным значением, иначе они не будут работать.

from tkinter import *  
from tkinter.ttk import Radiobutton  
  
  
window = Tk()  
window.title("Добро пожаловать в приложение PythonRu")  
window.geometry('400x250')  
rad1 = Radiobutton(window, text='Первый', value=1)  
rad2 = Radiobutton(window, text='Второй', value=2)  
rad3 = Radiobutton(window, text='Третий', value=3)  
rad1.grid(column=0, row=0)  
rad2.grid(column=1, row=0)  
rad3.grid(column=2, row=0)  
window.mainloop()

Результатом вышеприведенного кода будет следующий:

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

rad1 = Radiobutton(window,text='Первая', value=1, command=clicked)

def clicked():
    

Достаточно легко!

Получение значения Radio Button (Избранная Radio Button)

Чтобы получить текущую выбранную radio кнопку или ее значение, вы можете передать параметр переменной и получить его значение.

from tkinter import *  
from tkinter.ttk import Radiobutton  
  
  
def clicked():  
    lbl.configure(text=selected.get())  
  
  
window = Tk()  
window.title("Добро пожаловать в приложение PythonRu")  
window.geometry('400x250')  
selected = IntVar()  
rad1 = Radiobutton(window,text='Первый', value=1, variable=selected)  
rad2 = Radiobutton(window,text='Второй', value=2, variable=selected)  
rad3 = Radiobutton(window,text='Третий', value=3, variable=selected)  
btn = Button(window, text="Клик", command=clicked)  
lbl = Label(window)  
rad1.grid(column=0, row=0)  
rad2.grid(column=1, row=0)  
rad3.grid(column=2, row=0)  
btn.grid(column=3, row=0)  
lbl.grid(column=0, row=1)  
window.mainloop()

Каждый раз, когда вы выбираете radio button, значение переменной будет изменено на значение кнопки.

Добавление виджета ScrolledText (текстовая область Tkinter)

Чтобы добавить виджет ScrolledText, используйте класс ScrolledText:

from tkinter import scrolledtext


txt = scrolledtext. ScrolledText(window,width=40,height=10)

Здесь нужно указать ширину и высоту ScrolledText, иначе он заполнит все окно.

from tkinter import *  
from tkinter import scrolledtext  
  
  
window = Tk()  
window.title("Добро пожаловать в приложение PythonRu")  
window.geometry('400x250')  
txt = scrolledtext.ScrolledText(window, width=40, height=10)  
txt.grid(column=0, row=0)  
window.mainloop()

Результат:

Настройка содержимого Scrolledtext

Используйте метод insert, чтобы настроить содержимое Scrolledtext:

txt.insert(INSERT, 'Текстовое поле')

Удаление/Очистка содержимого Scrolledtext

Чтобы очистить содержимое данного виджета, используйте метод delete:

txt.delete(1.0, END)  

Отлично!

Создание всплывающего окна с сообщением

Чтобы показать всплывающее окно с помощью Tkinter, используйте messagebox следующим образом:

from tkinter import messagebox


messagebox. showinfo('Заголовок', 'Текст')

Довольно легко! Давайте покажем окно сообщений при нажатии на кнопку пользователем.

from tkinter import *  
from tkinter import messagebox  
  
  
def clicked():  
    messagebox.showinfo('Заголовок', 'Текст')  
  
  
window = Tk()  
window.title("Добро пожаловать в приложение PythonRu")  
window.geometry('400x250')  
btn = Button(window, text='Клик', command=clicked)  
btn.grid(column=0, row=0)  
window.mainloop()

Когда вы нажмете на кнопку, появится информационное окно.

Показ сообщений о предупреждениях и ошибках

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

messagebox.showwarning('Заголовок', 'Текст')  
messagebox.showerror('Заголовок', 'Текст')  

Показ диалоговых окон с выбором варианта

Чтобы показать пользователю сообщение “да/нет”, вы можете использовать одну из следующих функций messagebox:

from tkinter import messagebox


res = messagebox. askquestion('Заголовок', 'Текст')
res = messagebox.askyesno('Заголовок', 'Текст')
res = messagebox.askyesnocancel('Заголовок', 'Текст')
res = messagebox.askokcancel('Заголовок', 'Текст')
res = messagebox.askretrycancel('Заголовок', 'Текст')

Вы можете выбрать соответствующий стиль сообщения согласно вашим потребностям. Просто замените строку функции showinfo на одну из предыдущих и запустите скрипт. Кроме того, можно проверить, какая кнопка нажата, используя переменную результата.

Если вы кликнете OK, yes или retry, значение станет True, а если выберете no или cancel, значение будет False.
Единственной функцией, которая возвращает одно из трех значений, является функция askyesnocancel; она возвращает True/False/None.

Добавление SpinBox (Виджет спинбокс)

Для создания виджета спинбокса, используйте класс Spinbox:

spin = Spinbox(window, from_=0, to=100)

Таким образом, мы создаем виджет Spinbox, и передаем параметры from и to, чтобы указать диапазон номеров.
Кроме того, вы можете указать ширину виджета с помощью параметра width:

spin = Spinbox(window, from_=0, to=100, width=5)

Проверим пример полностью:

from tkinter import *  
  
  
window = Tk()  
window.title("Добро пожаловать в приложение PythonRu")  
window.geometry('400x250')  
spin = Spinbox(window, from_=0, to=100, width=5)  
spin.grid(column=0, row=0)  
window.mainloop()

Вы можете указать числа для Spinbox, вместо использования всего диапазона следующим образом:

spin = Spinbox(window, values=(3, 8, 11), width=5)

Виджет покажет только эти 3 числа: 3, 8 и 11.

Задать значение по умолчанию для Spinbox

В случае, если вам нужно задать значение по умолчанию для Spinbox, вы можете передать значение параметру textvariable следующим образом:

var = IntVar()
var.set(36)
spin = Spinbox(window, from_=0, to=100, width=5, textvariable=var)

Теперь, если вы запустите программу, она покажет 36 как значение по умолчанию для Spinbox.

Добавление виджета Progressbar

Чтобы создать данный виджет, используйте класс progressbar :

from tkinter.ttk import Progressbar


bar = Progressbar(window, length=200)

Установите значение progressbar таким образом:

bar['value'] = 70

Вы можете установить это значение на основе любого процесса или при выполнении задачи.

Изменение цвета Progressbar

Изменение цвета Progressbar немного сложно. Сначала нужно создать стиль и задать цвет фона, а затем настроить созданный стиль на Progressbar. Посмотрите следующий пример:

from tkinter import *  
from tkinter.ttk import Progressbar  
from tkinter import ttk  
  
  
window = Tk()  
window.title("Добро пожаловать в приложение PythonRu")  
window.geometry('400x250')  
style = ttk.Style()  
style.theme_use('default')  
style.configure("black.Horizontal.TProgressbar", background='black')  
bar = Progressbar(window, length=200,)  
bar['value'] = 70  
bar. grid(column=0, row=0)  
window.mainloop()

И в результате вы получите следующее:

Добавление поля загрузки файла

Для добавления поля с файлом, используйте класс filedialog:

from tkinter import filedialog


file = filedialog.askopenfilename()

После того, как вы выберете файл, нажмите “Открыть”; переменная файла будет содержать этот путь к файлу. Кроме того, вы можете запросить несколько файлов:

files = filedialog.askopenfilenames()

Указание типа файлов (расширение фильтра файлов)

Возможность указания типа файлов доступна при использовании параметра filetypes, однако при этом важно указать расширение в tuples.

file = filedialog.askopenfilename(filetypes = (("Text files","*.txt"),("all files","*.*")))

Вы можете запросить каталог, используя метод askdirectory :

dir = filedialog. askdirectory()

Вы можете указать начальную директорию для диалогового окна файла, указав initialdir следующим образом:

from os import path
file = filedialog.askopenfilename(initialdir= path.dirname(__file__))

Легко!

Добавление панели меню

Для добавления панели меню, используйте класс menu:

from tkinter import Menu


menu = Menu(window)
menu.add_command(label='Файл')
window.config(menu=menu)

Сначала мы создаем меню, затем добавляем наш первый пункт подменю. Вы можете добавлять пункты меню в любое меню с помощью функции add_cascade() таким образом:

menu.add_cascade(label='Автор', menu=new_item)

Наш код будет выглядеть так:

from tkinter import *  
from tkinter import Menu  
  
  
window = Tk()  
window.title("Добро пожаловать в приложение PythonRu")  
window. geometry('400x250')  
menu = Menu(window)  
new_item = Menu(menu)  
new_item.add_command(label='Новый')  
menu.add_cascade(label='Файл', menu=new_item)  
window.config(menu=menu)  
window.mainloop()

Таким образом, вы можете добавить столько пунктов меню, сколько захотите.

from tkinter import *  
  
  
window = Tk()  
window.title("Добро пожаловать в приложение PythonRu")  
window.geometry('400x250')  
menu = Menu(window)  
new_item = Menu(menu)  
new_item.add_command(label='Новый')  
new_item.add_separator()  
new_item.add_command(label='Изменить')  
menu.add_cascade(label='Файл', menu=new_item)  
window.config(menu=menu)  
window.mainloop()

Теперь мы добавляем еще один пункт меню “Изменить” с разделителем меню. Вы можете заметить пунктирную линию в начале, если вы нажмете на эту строку, она отобразит пункты меню в небольшом отдельном окне.

Можно отключить эту функцию, с помощью tearoff подобным образом:

new_item = Menu(menu, tearoff=0)

Просто отредактируйте new_item, как в приведенном выше примере и он больше не будет отображать пунктирную линию.
Вы так же можете ввести любой код, который работает, при нажатии пользователем на любой элемент меню, задавая свойство команды.

new_item.add_command(label='Новый', command=clicked)

Добавление виджета Notebook (Управление вкладкой)

Для удобного управления вкладками реализуйте следующее:

  • Для начала, создается элемент управления вкладкой, с помощью класса Notebook .
  • Создайте вкладку, используя класс Frame.
  • Добавьте эту вкладку в элемент управления вкладками.
  • Запакуйте элемент управления вкладкой, чтобы он стал видимым в окне.
from tkinter import *  
from tkinter import ttk  
  
  
window = Tk()  
window. title("Добро пожаловать в приложение PythonRu")  
window.geometry('400x250')  
tab_control = ttk.Notebook(window)  
tab1 = ttk.Frame(tab_control)  
tab_control.add(tab1, text='Первая')  
tab_control.pack(expand=1, fill='both')  
window.mainloop()

Таким образом, вы можете добавлять столько вкладок, сколько нужно.

Добавление виджетов на вкладку

После создания вкладок вы можете поместить виджеты внутри этих вкладок, назначив родительское свойство нужной вкладке.

from tkinter import *  
from tkinter import ttk  
  
  
window = Tk()  
window.title("Добро пожаловать в приложение PythonRu")  
window.geometry('400x250')  
tab_control = ttk.Notebook(window)  
tab1 = ttk.Frame(tab_control)  
tab2 = ttk.Frame(tab_control)  
tab_control.add(tab1, text='Первая')  
tab_control.add(tab2, text='Вторая')  
lbl1 = Label(tab1, text='Вкладка 1')  
lbl1.grid(column=0, row=0)  
lbl2 = Label(tab2, text='Вкладка 2')  
lbl2. grid(column=0, row=0)  
tab_control.pack(expand=1, fill='both')  
window.mainloop()

Добавление интервала для виджетов (Заполнение)

Вы можете добавить отступы для элементов управления, чтобы они выглядели хорошо организованными с использованием свойств padx иpady.

Передайте padx и pady любому виджету и задайте значение.

lbl1 = Label(tab1, text= 'label1', padx=5, pady=5)

Это очень просто!

В этом уроке мы увидели много примеров GUI Python с использованием библиотеки Tkinter. Так же рассмотрели основные аспекты разработки графического интерфейса Python. Не стоит на этом останавливаться. Нет учебника или книги, которая может охватывать все детали. Надеюсь, эти примеры были полезными для вас.

создаём простое приложение с PyQt и Qt Designer

Эта статья предназначена для тех, кто только начинает своё знакомство с созданием приложений с графическим интерфейсом (GUI) на Python. В ней мы рассмотрим основы использования PyQt в связке с Qt Designer. Шаг за шагом мы создадим простое Python GUI приложение, которое будет отображать содержимое выбранной директории.

Что нам потребуется

Нам понадобятся PyQt и Qt Designer, ну и Python, само собой.

В этой статье используется PyQt5 с Python 3, но особых различий между PyQt и PySide или их версиями для Python 2 нет.

Windows: PyQt можно скачать здесь. В комплекте с ним идёт Qt Designer.

macOS: Вы можете установить PyQt с помощью Homebrew:

$ brew install pyqt5

Скачать пакет с большинством компонентов и инструментов Qt, который содержит Qt Designer, можно по этой ссылке.

Linux: Всё нужное, вероятно, есть в репозиториях вашего дистрибутива. Qt Designer можно установить из Центра Приложений, но PyQt придётся устанавливать через терминал. Установить всё, что нам понадобится, одной командой можно, например, так:

# для Fedora:
$ sudo dnf install python3-qt5 qt-creator
# для Debian/Ubuntu:
$ sudo apt install python3-qt5 pyqt5-dev-tools qtcreator

После того как вы закончили с приготовлениями, откройте командную строку/терминал и убедитесь, что вы можете использовать команду pyuic5. Вы должны увидеть следующее:

$ pyuic5
Error: one input ui-file must be specified

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

Если вы используете Windows, то, скорее всего, путь C:\Python36\Scripts (измените 36 на вашу версию Python) не прописан в вашем PATH. Загляните в этот тред на Stack Overflow, чтобы узнать, как решить проблему.

Дизайн

Основы

Теперь, когда у нас всё готово к работе, давайте начнём с простого дизайна.

Откройте Qt Designer, где вы увидите диалог новой формы, выберите Main Window и нажмите Create.

После этого у вас должна появиться форма — шаблон для окна, размер которого можно менять и куда можно вставлять объекты из окна виджетов и т.д. Ознакомьтесь с интерфейсом, он довольно простой.

Теперь давайте немного изменим размер нашего главного окна, т. к. нам не нужно, чтобы оно было таким большим. А ещё давайте уберём автоматически добавленное меню и строку состояния, поскольку в нашем приложении они не пригодятся.

Все элементы формы и их иерархия по умолчанию отображаются в правой части окна Qt Designer под названием Object Inspector. Вы с лёгкостью можете удалять объекты, кликая по ним правой кнопкой мыши в этом окне. Или же вы можете выбрать их в основной форме и нажать клавишу DEL на клавиатуре.

В итоге мы имеем почти пустую форму. Единственный оставшийся объект — centralwidget, но он нам понадобится, поэтому с ним мы ничего не будем делать.

Теперь перетащите куда-нибудь в основную форму List Widget (не List View) и Push Button из Widget Box.

Макеты

Вместо использования фиксированных позиций и размеров элементов в приложении лучше использовать макеты. Фиксированные позиции и размеры у вас будут выглядеть хорошо (пока вы не измените размер окна), но вы никогда не можете быть уверены, что всё будет точно так же на других машинах и/или операционных системах.

Макеты представляют собой контейнеры для виджетов, которые будут удерживать их на определённой позиции относительно других элементов. Поэтому при изменении размера окна размер виджетов тоже будет меняться.

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

Теперь в меню Qt Designer нажмите Form, затем выберите Preview и увидите что-то похожее на скриншот выше. Выглядит хорошо, не так ли? Но вот что случится, когда мы изменим размер окна:

Наши объекты остались на тех же местах и сохранили свои размеры, несмотря на то что размер основного окна изменился и кнопку почти не видно. Вот поэтому в большинстве случаев стоит использовать макеты. Конечно, бывают случаи, когда вам, например, нужна фиксированная или минимальная/максимальная ширина объекта. Но вообще при разработке приложения лучше использовать макеты.

Основное окно уже поддерживает макеты, поэтому нам ничего не нужно добавлять в нашу форму. Просто кликните правой кнопкой мыши по Main Window в Object Inspector и выберите Lay outLay out vertically. Также вы можете кликнуть правой кнопкой по пустой области в форме и выбрать те же опции:

Ваши элементы должны быть в том же порядке, что и до внесённых изменений, но если это не так, то просто перетащите их на нужное место.

Так как мы использовали вертикальное размещение, все элементы, которые мы добавим, будут располагаться вертикально. Можно комбинировать размещения для получения желаемого результата. Например, горизонтальное размещение двух кнопок в вертикальном будет выглядеть так:

Если у вас не получается переместить элемент в главном окне, вы можете сделать это в окне Object Inspector.

Последние штрихи

Теперь, благодаря вертикальному размещению, наши элементы выровнены правильно. Единственное, что осталось сделать (но не обязательно), — изменить имя элементов и их текст.

В простом приложении вроде этого с одним лишь списком и кнопкой изменение имён не обязательно, так как им в любом случае просто пользоваться. Тем не менее правильное именование элементов — то, к чему стоит привыкать с самого начала.

Свойства элементов можно изменить в разделе Property Editor.

Подсказка: вы можете менять размер, передвигать или добавлять часто используемые элементы в интерфейс Qt Designer для ускорения рабочего процесса. Вы можете добавлять скрытые/закрытые части интерфейса через пункт меню View.

Нажмите на кнопку, которую вы добавили в форму. Теперь в Property Editor вы должны видеть все свойства этого элемента. В данный момент нас интересуют objectName и text в разделе QAbstractButton. Вы можете сворачивать разделы в Property Editor нажатием по названию раздела.

Измените значение objectName на btnBrowse и text на Выберите папку.

Должно получиться так:

Именем объекта списка является listWidget, что вполне подходит в данном случае.

Сохраните дизайн как design. ui в папке проекта.

Превращаем дизайн в код

Конечно, можно использовать .ui-файлы напрямую из Python-кода, однако есть и другой путь, который может показаться легче. Можно конвертировать код .ui-файла в Python-файл, который мы потом сможем импортировать и использовать. Для этого мы используем команду pyuic5 из терминала/командной строки.

Чтобы конвертировать .ui-файл в Python-файл с названием design.py, используйте следующую команду:

$ pyuic5 path/to/design.ui -o output/path/to/design.py

Пишем код

Теперь у нас есть файл design.py с нужной частью дизайна нашего приложения и мы начинать работу над созданием его логики.

Создайте файл main.py в папке, где находится design.py.

Другие интересные статьи по Python.

Используем дизайн

Для Python GUI приложения понадобятся следующие модули:

import sys  # sys нужен для передачи argv в QApplication
from PyQt5 import QtWidgets

Также нам нужен код дизайна, который мы создали ранее, поэтому его мы тоже импортируем:

import design  # Это наш конвертированный файл дизайна

Так как файл с дизайном будет полностью перезаписываться каждый раз при изменении дизайна, мы не будем изменять его. Вместо этого мы создадим новый класс ExampleApp, который объединим с кодом дизайна для использования всех его функций:

class ExampleApp(QtWidgets.QMainWindow, design.Ui_MainWindow):
    def __init__(self):
        # Это здесь нужно для доступа к переменным, методам
        # и т.д. в файле design.py
        super().__init__()
        self.setupUi(self)  # Это нужно для инициализации нашего дизайна

В этом классе мы будем взаимодействовать с элементами интерфейса, добавлять соединения и всё остальное, что нам потребуется. Но для начала нам нужно инициализировать класс при запуске кода. С этим мы разберёмся в функции main():

def main():
    app = QtWidgets.QApplication(sys.argv)  # Новый экземпляр QApplication
    window = ExampleApp()  # Создаём объект класса ExampleApp
    window.show()  # Показываем окно
    app.exec_()  # и запускаем приложение

И чтобы выполнить эту функцию, мы воспользуемся привычной конструкцией:

if __name__ == '__main__':  # Если мы запускаем файл напрямую, а не импортируем
    main()  # то запускаем функцию main()

В итоге main. py выглядит таким образом:

import sys  # sys нужен для передачи argv в QApplication
from PyQt5 import QtWidgets
import design  # Это наш конвертированный файл дизайна

class ExampleApp(QtWidgets.QMainWindow, design.Ui_MainWindow):
    def __init__(self):
        # Это здесь нужно для доступа к переменным, методам
        # и т.д. в файле design.py
        super().__init__()
        self.setupUi(self)  # Это нужно для инициализации нашего дизайна

def main():
    app = QtWidgets.QApplication(sys.argv)  # Новый экземпляр QApplication
    window = ExampleApp()  # Создаём объект класса ExampleApp
    window.show()  # Показываем окно
    app.exec_()  # и запускаем приложение

if __name__ == '__main__':  # Если мы запускаем файл напрямую, а не импортируем
    main()  # то запускаем функцию main()

Если запустить этот код: $ python3 main.py, то наше приложение запустится!

Но нажатие на кнопку ничего не даёт, поэтому нам придётся с этим разобраться.

Добавляем функциональность в наше Python GUI приложение

Примечание Весь дальнейший код пишется внутри класса ExampleApp.

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

self.btnBrowse.clicked.connect(self.browse_folder)

Добавьте эту строку в метод __init__ класса ExampleApp, чтобы выполнить привязку при запуске приложения. А теперь взглянем на неё поближе:

  • self.btnBrowse: здесь btnBrowse — имя объекта, который мы определили в Qt Designer. self говорит само за себя и означает принадлежность к текущему классу;
  • clicked — событие, которое мы хотим привязать. У разных элементов разные события, например, у виджетов списка есть itemSelectionChanged и т.д.;
  • connect() — метод, который привязывает событие к вызову переданной функции;
  • self. browse_folder — просто функция (метод), которую мы описали в классе ExampleApp.

Для открытия диалога выбора папки мы можем использовать встроенный метод QtWidgets.QFileDialog.getExistingDirectory:

directory = QtWidgets.QFileDialog.getExistingDirectory(self, "Выберите папку")

Если пользователь выберет директорию, переменной directory присвоится абсолютный путь к выбранной директории, в противном случае она будет равна None. Чтобы не выполнять код дальше, если пользователь закроет диалог, мы используем команду if directory:.

Для отображения содержимого директории нам нужно импортировать os:

import os

И получить список содержимого следующим образом:

os.listdir(path)

Для добавления элементов в listWidget мы используем метод addItem(), а для удаления всех элементов у нас есть self. listWidget.clear().

В итоге функция browse_folder должна выглядеть так:

def browse_folder(self):
    self.listWidget.clear()  # На случай, если в списке уже есть элементы
    directory = QtWidgets.QFileDialog.getExistingDirectory(self, "Выберите папку")
    # открыть диалог выбора директории и установить значение переменной
    # равной пути к выбранной директории

    if directory:  # не продолжать выполнение, если пользователь не выбрал директорию
        for file_name in os.listdir(directory):  # для каждого файла в директории
            self.listWidget.addItem(file_name)   # добавить файл в listWidget

Теперь, если запустить приложение, нажать на кнопку и выбрать директорию, мы увидим:

Так выглядит весь код нашего Python GUI приложения:

import sys  # sys нужен для передачи argv в QApplication
import os  # Отсюда нам понадобятся методы для отображения содержимого директорий

from PyQt5 import QtWidgets

import design  # Это наш конвертированный файл дизайна

class ExampleApp(QtWidgets. QMainWindow, design.Ui_MainWindow):
    def __init__(self):
        # Это здесь нужно для доступа к переменным, методам
        # и т.д. в файле design.py
        super().__init__()
        self.setupUi(self)  # Это нужно для инициализации нашего дизайна
        self.btnBrowse.clicked.connect(self.browse_folder)  # Выполнить функцию browse_folder
                                                            # при нажатии кнопки

    def browse_folder(self):
        self.listWidget.clear()  # На случай, если в списке уже есть элементы
        directory = QtWidgets.QFileDialog.getExistingDirectory(self, "Выберите папку")
        # открыть диалог выбора директории и установить значение переменной
        # равной пути к выбранной директории

        if directory:  # не продолжать выполнение, если пользователь не выбрал директорию
            for file_name in os.listdir(directory):  # для каждого файла в директории
                self. listWidget.addItem(file_name)   # добавить файл в listWidget

def main():
    app = QtWidgets.QApplication(sys.argv)  # Новый экземпляр QApplication
    window = ExampleApp()  # Создаём объект класса ExampleApp
    window.show()  # Показываем окно
    app.exec_()  # и запускаем приложение

if __name__ == '__main__':  # Если мы запускаем файл напрямую, а не импортируем
    main()  # то запускаем функцию main()

 


Это были основы использования Qt Designer и PyQt для разработки Python GUI приложения. Теперь вы можете спокойно изменять дизайн приложения и использовать команду pyuic5 без страха потерять написанный код.

Перевод статьи «PyQt: Getting started with PyQt and Qt Designer»

Tkinter. Программирование GUI на Python. Курс

Курс «Tkinter. Программирование GUI на Python» знакомит с особенностями создания графического интерфейса пользователя средствами пакета tkinter языка программирования Python. Требует знания языка Python на уровне структурного программирования, желательно также владение азами ООП.

В курсе уделяется внимание работе с базовыми элементами интерфейса (виджетами). Изучаются основные свойства и методы кнопок, меток, текстовых полей, списков, флажков и радиокнопок, холста и меню.

Рассматриваются различные способы размещения виджетов в родительском окне: простой упаковкой, табличным разбиением и с помощью координат.

Изучаются настройки главного окна приложения, диалоговые окна, модуль tkinter.ttk.

Версия курса: май 2021 г.

Курс с примерами решений практических работ:
android-приложение,
pdf-версия.

Что такое Tkinter
Tkinter — это пакет модулей Python для создания приложений с графическим интерфейсом пользователя. Tkinter используется в Python по-умолчанию. В уроке рассматривается последовательность шагов создания GUI-программы: импорт, создание окна и виджетов, установка свойств, событий и др.

Виджеты Button, Label, Entry
В tkinter от класса Button создаются кнопки, от Label — метки, от Entry — однострочные текстовые поля для ввода. В уроке рассматривают основные свойства и методы данных виджетов.

Метод pack
В tkinter с помощью метода pack виджеты располагаются в родительском окне или фрейме. В уроке описываются опции pack, позволяющие конфигурировать виджеты относительно друг друга и относительно родителя.

Text – многострочное текстовое поле
В tkinter от класса Text создаются виджеты многострочного текстового поля. В уроке рассматриваются свойства и методы Text, настройка Scrollbar (скроллера), создание и конфигурирование тегов.

Radiobutton и Checkbutton. Переменные Tkinter
В tkinter от классов Radiobutton и Checkbutton создаются радиокнопки и флажки. В уроке описываются их основные свойства и методы, а также переменные tkinter, позволяющие связывать радиокнопки в группы и снимать данные с флажков.

Виджет Listbox
В tkinter от класса LIstbox создаются виджеты-списки, позволяющие выбирать один или множество элементов. В уроке рассматривается как заполнять список, удалять из него значения, считывать выбранные элементы.

Метод bind
В Tkinter метод bind связывает виджет, событие и функцию-обработчик: widget.bind(event, function). В уроке рассматриваются особенности передачи функций в метод bind.

События
В GUI событиями являются клики и движение мышью, нажатие клавиш на клавиатуре, изменение виджетов. События — это особые объекты Tkinter, имеющие свои атрибуты: тип, виджет, по отношению к которому произошло событие и др.

Canvas
От класса Canvas в tkinter создаются холсты, на которых можно с помощью специальных методов рисовать геометрические фигуры и размещать объекты. В уроке рассматривается создание на холсте геометрических примитивов (отрезков, многоугольников, эллипсов и др.) и текста.

Canvas. Идентификаторы, теги и анимация
Canvas — это холст, на котором можно программно выводить геометрические фигуры и другие объекты, к которым можно обращаться с помощью идентификаторов и тегов с целью изменения их свойств в процессе выполнения программы. Это позволяет в том числе делать холст интерактивным, создавать анимацию.

Окна
Окна в tkinter создаются от классов Tk и Toplevel, который используется в случае программирования многооконных приложений. Метод geometry позволяет задавать размер окна и его положение на экране. С помощью метода resizable можно запретить изменение размеров окна пользователем. title задает название окна в заголовке.

Метод grid
Grid — сетка, таблица — один из трех управляющих размещением, или менеджеров геометрии, Tkinter. Реализуется через метод grid виджетов. Опции: row, column, rowspan, columnspan, padx, pady, ipadx, ipady, sticky. Grid более удобен при разработке сложных GUI, чем Pack.

Диалоговые окна
В модулях messagebox и filedialog пакета tkinter содержатся функции вызова стандартных диалоговых окон — askyesno, askyesnocancel, showerror, showinfo, askopenfilename, asksaveasfilename и др.

Виджет Menu
В tkinter меню создаются от класса Menu. Для привязки списков используется метод add_cascade, для создания команд — add_command. Для создания всплывающего меню используется метод post.

Метод place
В tkinter метод place размещает виджеты по координатам. Возможно указание как абсолютных координат (x и y), так относительных (relx и rely). Размеры виджетов также могут быть абсолютными (width, height) и относительными (relwidth, relheight).

Модуль tkinter.ttk
В состав пакета tkinter входит модуль ttk, содержащий классы более стилизованных виджет, темы их оформления, а также классы и методы для переопределения их внешнего вида.

Графический интерфейс на Python за 5 минут

Python легко использовать. В нем вы можете найти огромное количество библиотек для чего угодно. И это его основное преимущество. Из нескольких строк кода вы ничего не сделаете. Если вам нужны скрипты для личного пользования или для технически подкованной аудитории, то вам даже не придется думать о графическом интерфейсе.

Однако иногда ваша целевая аудитория не сильно подкована технически. Люди не против использовать ваши скрипты на Python до тех пор пока им не нужно смотреть на одну строку кода. В таком случае скриптов командной строки будет недостаточно. В идеале вам нужен графический интерфейс. Цель этого поста использовать только Python.

Библиотеки Python, которые можно использовать для графического интерфейса

По сути, есть 3 большие библиотеки Python для графического интерфейса; Tkinter, wxPython и PyQT. Рассматривая их, я не нашел там ничего из того, что мне нравится в Python. Библиотеки Python, как правило, очень хорошо абстрагируются от супер-технических моментов. Если бы мне нужно было работать с объектно-ориентированным программированием, я мог бы с таким же успехом загрузить Java или .Net.



Статья переведена при поддержке компании EDISON Software, которая заботится о здоровье программистов и их завтраке, а также разрабатывает программное обеспечение на заказ.

Однако, к счастью, я наткнулся на четвёртый вариант, который был мне по душе. Это PySimpleGUI, я до сих пор ей пользуюсь. Как ни странно, эта библиотека использует все 3 популярные библиотеки, о которых шла речь выше, но при этом абстрагируется от супер технических моментов

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

Проверьте два одинаковых файла

Я рассказал как это сделать в своей статье “3 быстрых способа сравнить данные в Python”. Мы можем использовать первый раздел, проверку целостности данных, чтобы попытаться создать пользовательский интерфейс.

  • 3 Quick Ways To Compare Data with Python

По факту нам нужно загрузить два файла и выбрать шифрование, которое мы хотели бы использовать для сравнения файлов.

Запрограммируйте графический интерфейс

Чтобы создать графический интерфейс, можно использовать этот код:

import PySimpleGUI as sg
layout = [
    [sg.Text('File 1'), sg.InputText(), sg.FileBrowse(),
     sg.Checkbox('MD5'), sg.Checkbox('SHA1')
     ],
    [sg.Text('File 2'), sg.InputText(), sg.FileBrowse(),
     sg.Checkbox('SHA256')
     ],
    [sg.Output(size=(88, 20))],
    [sg.Submit(), sg.Cancel()]
]
window = sg.Window('File Compare', layout)
while True:                             # The Event Loop
    event, values = window.read()
    # print(event, values) #debug
    if event in (None, 'Exit', 'Cancel'):
        break

в результате мы получим:

Подключаем логику

Когда есть пользовательский интерфейс, легко понять, как подключить остальную часть кода. Нам просто нужно следить за тем, что вводит пользователь и действовать соответственно. Мы можем очень легко сделать это с помощью следующего кода:

import PySimpleGUI as sg
import re
import hashlib
def hash(fname, algo):
    if algo == 'MD5':
        hash = hashlib.md5()
    elif algo == 'SHA1':
        hash = hashlib.sha1()
    elif algo == 'SHA256':
        hash = hashlib.sha256()
    with open(fname) as handle: #opening the file one line at a time for memory considerations
        for line in handle:
            hash.update(line.encode(encoding = 'utf-8'))
    return(hash.hexdigest())
layout = [
    [sg.Text('File 1'), sg.InputText(), sg.FileBrowse(),
     sg.Checkbox('MD5'), sg.Checkbox('SHA1')
     ],
    [sg.Text('File 2'), sg.InputText(), sg.FileBrowse(),
     sg.Checkbox('SHA256')
     ],
    [sg.Output(size=(88, 20))],
    [sg.Submit(), sg.Cancel()]
]
window = sg.Window('File Compare', layout)
while True:                             # The Event Loop
    event, values = window.read()
    # print(event, values) #debug
    if event in (None, 'Exit', 'Cancel'):
        break
    if event == 'Submit':
        file1 = file2 = isitago = None
        # print(values[0],values[3])
        if values[0] and values[3]:
            file1 = re.findall('.+:\/.+\.+.', values[0])
            file2 = re.findall('.+:\/.+\.+.', values[3])
            isitago = 1
            if not file1 and file1 is not None:
                print('Error: File 1 path not valid.')
                isitago = 0
            elif not file2 and file2 is not None:
                print('Error: File 2 path not valid.')
                isitago = 0
            elif values[1] is not True and values[2] is not True and values[4] is not True:
                print('Error: Choose at least one type of Encryption Algorithm')
            elif isitago == 1:
                print('Info: Filepaths correctly defined.')
                algos = [] #algos to compare
                if values[1] == True: algos.append('MD5')
                if values[2] == True: algos.append('SHA1')
                if values[4] == True: algos.append('SHA256')
                filepaths = [] #files
                filepaths.append(values[0])
                filepaths.append(values[3])
                print('Info: File Comparison using:', algos)
                for algo in algos:
                    print(algo, ':')
                    print(filepaths[0], ':', hash(filepaths[0], algo))
                    print(filepaths[1], ':', hash(filepaths[1], algo))
                    if hash(filepaths[0],algo) == hash(filepaths[1],algo):
                        print('Files match for ', algo)
                    else:
                        print('Files do NOT match for ', algo)
        else:
            print('Please choose 2 files.')
window.close()

он даст нам такой результат:

Заключительные мысли

Может это и не самый красивый пользовательский интерфейс, но PySimpleGUI позволяет вам быстро разворачивать простые пользовательские интерфейсы Python и делиться ими с кем угодно. Код, который вам нужен для этого, прост и легко читается. У вас все еще будет проблема запуска кода для получения пользовательского интерфейса. Из-за этого могут возникнуть сложности с совместным использованием кода. Советую скачать что-то вроде PyInstaller, который превратит ваш скрипт на python в .exe файл. Люди смогут запустить его просто нажав на него дважды.

Лучший фреймворк Python для создания приложения и графического интерфейса

Python был самым популярным языком программирования, используемым для объектно-ориентированного программирования. С Python вы можете запускать простые операторы без необходимости компилировать целую программу.

Конечно, Python – это интерактивное программирование, которое имеет широкий спектр возможностей для графического интерфейса пользователя GUI (помогает разработчикам создавать приложения с графическим интерфейсом простым и безопасным способом). В этой статье обсуждается лучшие фреймворки Python для создания настольного приложения и графического интерфейса.

Очень важно, чтобы у вас были базовые знания языка программирования Python, прежде чем вы сможете использовать эти среды Python.

Давайте приступим.

PyQT

PyQt – это набор инструментов для графического интерфейса пользователя. Это один из самых мощных и популярных интерфейсов Python. Это комбинация библиотеки Qt (принадлежит Nokia) и языка программирования Python, которая позволяет разработчику решать, создавать ли программу путем кодирования или создавать визуальные диалоги с использованием Qt Designer.

PyQt – это бесплатное программное обеспечение для разработки виджетов Qt с открытым исходным кодом, реализованное для кросс-платформенной среды разработки приложений. В бесплатной версии некоторые функции могут быть недоступны, но если ваше приложение имеет открытый исходный код, вы можете использовать его под свободной лицензией.

PyQt доступен для Windows, MacOSX, Linux, Android iOS и Raspberry Pi и различных версий Python от v2 до Qt v5.

Tkinter

Tkinter – самый популярный программный пакет для графического интерфейса пользователя или настольных приложений. Tkinter – это сочетание стандартного графического интерфейса Tk и Python.

TKinter поставляется с обилием ресурсов кодов и справочников, что является основной заслугой выбора его в качестве пакета. Он предоставляет разнообразные виджеты, такие как метки, кнопки и текстовые поля, используемые в приложении с графическим интерфейсом пользователя. Элемент управления Button, также называемый виджетами, используется для отображения кнопок в разрабатываемом приложении, тогда как виджет Canvas используется для рисования фигур (линий, овалов, многоугольников …) в вашем приложении.

Легко получить помощь, когда вы сталкиваетесь с препятствиями в процессе разработки вашего приложения, поскольку Tkinter имеет тысячи пользователей, потому что оно используется в течение очень долгого времени. Tkinter является открытым исходным кодом и доступен под лицензией Python.

Kivy

Kivy как ускоренная среда OpenGL ES 2 для создания новых пользовательских интерфейсов дает вам возможность с легкостью написать свой код один раз и запустить его на разных платформах или в операционных системах (Windows, MacOSX, Linux, Android iOS и Raspberry Pi).

Создавать приложения Kivy весело, легко и полезно, так как Kivy бесплатный и представляет собой библиотеку Python с открытым исходным кодом для создания прикладного программного обеспечения с включенным естественным пользовательским интерфейсом. У Kivy имеются двадцать виджетов в своем наборе инструментов.

WxPython

WxPython – это модуль расширения Python. Это также оболочка с открытым исходным кодом для библиотеки кроссплатформенного графического интерфейса пользователя Widget.

Как разработчик, вы можете разрабатывать традиционные приложения для Windows, Unix и Mac OS.

PyGUI

PyGUI – самый простой и легкий из всех графических интерфейсов, потому что он полностью синхронизирован с языком программирования Python. Это кроссплатформенная графическая прикладная среда для Windows, Mac OS и Unix.

Разработчик PyGUI вставляет очень мало кода между платформой GUI и приложением Python, что, в свою очередь, отображает естественный графический интерфейс платформы.

Итог

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

Понравилось то, что вы прочитали?

Подписывайтесь на нашу рассылку и получайте ежедневные обновления о новых учебниках, статьях, курсах и о многом другом! 

Просто введите ваш адрес электронной почты, чтобы подписаться.

(Без спамов; ежемесячно два письма; отписаться от рассылки можно в любое время)

Спасибо!

Подписка успешно оформлена. 🙂

Создание очень простых графических пользовательских интерфейсов с помощью Python с использованием appJar (интерфейс на основе Tkinter)

Хотя для python существует множество UI-фреймворков, большинство из них не так просты в использовании и реализации. По крайней мере, не для начинающего программиста на языке Python, поэтому сегодня мы хотим представить, как создать базовый пользовательский интерфейс с наиболее распространенные элементы пользовательского интерфейса например dropbox, радиобоксы, ввод текста, кнопки и т. д. с использованием пакета appJar для Python.

Что такое appJar

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

Графический интерфейс в Python довольно трудно реализовать самостоятельно, поэтому, если вы новичок, который хочет начать работать с python и создать свой собственный интерфейс для простых приложений, appJar — одно из самых надежных решений.

1. Установите appJar

Предпочтительный метод установки appJar через пипс. Откройте свой терминал и выполните следующую команду:

python -m pip install appjar

После установки вы можете обновить пакет, когда захотите:

python -m pip install appjar --upgrade

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

2. Использование AppJar

В отличие от многих других инструментов для визуального дизайна графического интерфейса, таких как visual studio, вам нужно будет создать свой собственный графический интерфейс с простым кодом, то есть строкой на строку. AppJar предлагает множество компонентов, поэтому перечислять их здесь не уместно. Поэтому мы опишем только особенности библиотеки и то, как легко ее можно использовать.

  • Разработан так, чтобы быть максимально простым, но при этом обеспечивать большую функциональность tkinter.
  • Предоставляет 3 функции для большинства виджетов:
    • add (имя, значение) это добавляет новый виджет (обычно с именем и значением)
    • set (name, value) это обновляет значение именованного виджета
    • get (name) это получает значение названного виджета
  • Использует сетку
  • При добавлении виджетов можно указать до 4 числовых «позиций»:
    • столбец — столбец для отображения, начиная с 0
    • ряд — строка, чтобы появиться, заявляя в 0
    • columnspan — сколько столбцов охватывать
    • rowspan — сколько строк пролегать вниз
  • Обеспечивает загрузку лишних битов и кусков вне ядра ткинтера
    • Часть этого была из превосходных ресурсов. http://effbot.org
    • Часть этого была из примеров slashdot о том, как решить общие проблемы
    • Часть этого была включена из модулей других людей:
      • Подсказка Майкла Ланге
      • Поддержка tkinter_png Иоганна Рохолла
      • Библиотека Мартина Дж. Фидлера NanoJPEG
  • Я пытался получить как можно больше функциональности в этой библиотеке, не требуя никаких других модулей

Инициализация приложения с пользовательским интерфейсом может быть такой простой, как следующий код:

# import the library
from appJar import gui
# create a GUI variable called app
app = gui()
# add & configure widgets - widgets get a name, to help referencing them later
app.addLabel("title", "Welcome to appJar")
app.setLabelBg("title", "red")
# start the GUI
app.go()

Что делает библиотеку довольно простой в использовании с простыми сценариями.

пример

В следующем примере реализована очень простая форма входа в систему, которая следует за приложением со структурой классов с некоторыми пользовательскими методами (они не следуют никакому шаблону). Чтобы запустить его, создайте новый файл Python, а именно main.py и поместите следующее:

# main.py
from appJar import gui
# Example of a semi-structured application
class MyApplication():
# Build the GUI
def Prepare(self, app):
# Form GUI
app.setTitle("Login Form")
app.setFont(16)
app.setStopFunction(self.BeforeExit)
# Add labels & entries
# in the correct row & column
app.addLabel("userLab", "Username:", 0, 0)
app.addEntry("username", 0, 1)
app.addLabel("passLab", "Password:", 1, 0)
app.addSecretEntry("password", 1, 1)
app.addButtons( ["Submit", "Cancel"], self.Submit, colspan=2)
return app
# Build and Start your application
def Start(self):
# Creates a UI
app = gui()
# Run the prebuild method that adds items to the UI
app = self.Prepare(app)
# Make the app class-accesible
self.app = app
# Start appJar
app.go()
# Callback execute before quitting your app
def BeforeExit(self):
return self.app.yesNoBox("Confirm Exit", "Are you sure you want to exit the application?")
# Define method that is executed when the user clicks on the submit buttons
# of the form
def Submit(self, btnName):
if btnName == "Submit":
username = self.app.getEntry("username")
password = self.app.getEntry("password")
# Very stupid login system (both strings equal to ourcodeworld)
if username and password == "ourcodeworld":
self.app.infoBox("Logged in", "You are now logged in !")
else:
self.app.errorBox("Error", "Your credentials are invalid !")
# Run the application
# `python main.py`
if __name__ == '__main__':
# Create an instance of your application
App = MyApplication()
# Start your app !
App.Start()

Затем запустите скрипт в вашем терминале, используя:

python main.py

Откроется новое окно с вашим основным пользовательским интерфейсом, как показано на следующем рисунке:

oop — Как мне реализовать интерфейсы в Python?

Реализация интерфейсов с абстрактными базовыми классами намного проще в современном Python 3, и они служат в качестве контракта интерфейса для расширений подключаемых модулей.

Создайте интерфейс / абстрактный базовый класс:

  из abc import ABC, abstractmethod

класс AccountingSystem (ABC):

    @abstractmethod
    def create_purchase_invoice (self, покупка):
        проходить

    @abstractmethod
    def create_sale_invoice (self, sale):
        бревно.отладка ('Создание счета-фактуры', продажа)
  

Создайте нормальный подкласс и переопределите все абстрактные методы:

  класс GizmoAccountingSystem (AccountingSystem):

    def create_purchase_invoice (self, покупка):
        submit_to_gizmo_purchase_service (покупка)

    def create_sale_invoice (self, sale):
        super (). create_sale_invoice (продажа)
        submit_to_gizmo_sale_service (продажа)
  

При желании вы можете иметь общую реализацию в абстрактных методах, как в create_sale_invoice () , вызывая его с помощью super () явно в подклассе, как указано выше.

Не удается создать подкласс, который не реализует все абстрактные методы:

  класс IncompleteAccountingSystem (AccountingSystem):
    проходить

>>> Accounting = IncompleteAccountingSystem ()
Отслеживание (последний вызов последний):
  Файл "", строка 1, в 
TypeError: невозможно создать экземпляр абстрактного класса IncompleteAccountingSystem с абстрактными методами
create_purchase_invoice, create_sale_invoice
  

Вы также можете иметь абстрактные свойства, статические методы и методы классов, комбинируя соответствующие аннотации с @abstractmethod .

Абстрактные базовые классы отлично подходят для реализации систем на основе плагинов. Все импортированные подклассы класса доступны через __subclasses __ () , поэтому, если вы загружаете все классы из каталога подключаемых модулей с помощью importlib.import_module () и если они являются подклассами базового класса, у вас есть прямой доступ к ним через __subclasses__ () , и вы можете быть уверены, что контракт интерфейса применяется ко всем из них во время создания экземпляра.

Вот реализация загрузки плагина для приведенного выше примера AccountingSystem :

 ...
из importlib import import_module

класс AccountingSystem (ABC):

    ...
    _instance = Нет

    @classmethod
    экземпляр def (cls):
        если не cls._instance:
            module_name = settings.ACCOUNTING_SYSTEM_MODULE_NAME
            import_module (имя_модуля)
            подклассы = cls .__ подклассы __ ()
            если len (подклассы)> 1:
                поднять InvalidAccountingSystemError ('Более одного'
                        f'счетный модуль: {подклассы} ')
            если не подклассы или имя_модуля не в str (подклассы [0]):
                поднять InvalidAccountingSystemError ('Модуль учета'
                        f '{module_name} не существует или не существует'
                        'subclass AccountingSystem')
            cls._instance = подклассы [0] ()
        вернуть cls._instance
  

Затем вы можете получить доступ к объекту плагина системы учета через класс AccountingSystem :

  >>> accountingsystem = AccountingSystem.instance ()
  

(На основе этого сообщения PyMOTW-3.)

Протоколы и азбуки · Абу Ашраф Маснун

Идея интерфейса очень проста — это описание поведения объекта. Интерфейс сообщает нам, что объект может делать, чтобы играть свою роль в системе.В объектно-ориентированном программировании интерфейс — это набор общедоступных методов объекта, которые могут использоваться другими частями программы для взаимодействия с этим объектом. Интерфейсы устанавливают четкие границы и помогают нам лучше организовать наш код. В некоторых языках, таких как Java, интерфейсы являются частью синтаксиса языка и строго соблюдаются. Однако в Python все немного иначе. В этом посте мы рассмотрим, как интерфейсы могут быть реализованы в Python.

Неформальные интерфейсы: протоколы / утиный ввод

В Python нет ключевого слова interface .Способ использования интерфейсов Java / C # здесь недоступен. В динамическом языковом мире все более неявно. Мы больше сосредоточены на поведении объекта, а не на его типе / классе.

Если он говорит и ходит как утка, значит, это утка

Итак, если у нас есть объект, который может летать и крякать, как утка, мы рассматриваем его как утку. Это называется «Утиный ввод». Во время выполнения вместо проверки типа объекта мы пытаемся вызвать метод, который мы ожидаем от объекта.Если он ведет себя так, как мы ожидали, у нас все в порядке, и мы идем дальше. Но если этого не произойдет, все может взорваться. В целях безопасности мы часто обрабатываем исключения в блоке try..except или используем hasattr , чтобы проверить, имеет ли объект конкретный метод.

В мире Python мы часто слышим «подобный файлу объект» или «итеративный» — если объект имеет метод read , его можно рассматривать как объект, подобный файлу, если он имеет магический метод __iter__ , это итерация.Таким образом, любой объект, независимо от его класса / типа, может соответствовать определенному интерфейсу, просто реализуя ожидаемое поведение (методы). Эти неформальные интерфейсы называются протоколами . Поскольку они носят неформальный характер, они не могут быть принудительно исполнены. В основном они проиллюстрированы в документации или определены условно. Все классные магические методы, о которых вы слышали — __len__ , __contains__ , __iter__ — все они помогают объекту соответствовать каким-то протоколам.

  класс Команда:
    def __init __ (я, члены):
        self .__ members = участники

    def __len __ (сам):
        return len (self .__ members)

    def __contains __ (я, член):
        вернуть член в self .__ members


Justice_league_fav = Команда (["Бэтмен", "чудо-женщина", "вспышка"])

# Размер протокола
печать (len (Justice_league_fav))

# Контейнерный протокол
print ("Бэтмен" в Justice_league_fav)
print ("супермен" в Justice_league_fav)
print ("киборг" отсутствует в Justice_league_fav)
  

В нашем примере выше, реализовав методы __len__ и __contains__ , теперь мы можем напрямую использовать функцию len в экземпляре Team и проверять членство с помощью оператора в .Если мы добавим метод __iter__ для реализации итеративного протокола, мы даже сможем сделать что-то вроде:

 
для участника Justice_league_fav:
    печать (член)

  

Без реализации метода __iter__ , если мы попытаемся выполнить итерацию по команде, мы получим ошибку типа:

  TypeError: объект "Команда" не повторяется.
  

Итак, мы видим, что протоколы подобны неформальным интерфейсам. Мы можем реализовать протокол, реализовав ожидаемые методы.

Формальные интерфейсы: ABCs

Хотя протоколы во многих случаях работают нормально, бывают ситуации, когда неформальные интерфейсы или утиная печать в целом могут вызвать путаницу. Например, Bird и Airplane могут летать () . Но это не одно и то же, даже если они реализуют одни и те же интерфейсы / протоколы. Абстрактные базовые классы или ABC могут помочь решить эту проблему.

Концепция ABC проста — мы определяем базовые классы, которые являются абстрактными по своей природе.Мы определяем определенные методы базовых классов как абстрактные методы. Таким образом, любые объекты, производные от этих базовых классов, вынуждены реализовывать эти методы. А поскольку мы используем базовые классы, если мы видим, что у объекта есть наш класс в качестве базового класса, мы можем сказать, что этот объект реализует интерфейс. Теперь мы можем использовать типы, чтобы определить, реализует ли объект определенный интерфейс. Давайте посмотрим на пример.

  импорт abc

класс Bird (abc.ABC):
    @ abc.abstractmethod
    def fly (self):
        проходить

  

Есть модуль abc , у которого есть метакласс с именем ABCMeta .Азбуки создаются из этого метакласса. Таким образом, мы можем либо использовать его напрямую как метакласс нашего ABC (что-то вроде этого — class Bird (metaclass = abc.ABCMeta): ), либо мы можем создать подкласс из класса abc.ABC , который имеет abc.ABCMeta , поскольку это уже метакласс.

Затем мы должны использовать декоратор abc.abstractmethod , чтобы пометить наши методы как абстрактные. Теперь, если какой-либо класс является производным от нашего базового класса Bird , он также должен реализовывать метод fly .Следующий код не сработает:

  класс Попугай (Птица):
    проходить

p = Попугай ()

  

Мы видим следующую ошибку:

  TypeError: не удается создать экземпляр абстрактного класса Parrot с абстрактными методами fly
  

Давайте исправим это:

 
класс Попугай (Bird):
    def fly (self):
        print ("Летающий")


p = Попугай ()
  

Также обратите внимание:

  >>> isinstance (p, Птица)
Правда

  

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

Теперь давайте определим еще один ABC с именем Airplane следующим образом:

 Самолет класса  (abc.ABC):
    @ abc.abstractmethod
    def fly (self):
        проходить


класс Боинг (Самолет):
    def fly (self):
        print ("Летим!")

b = Боинг ()

  

Теперь сравним:

 
>>> isinstance (p, Самолет)
Ложь
>>> isinstance (b, Птица)
Ложь
  

Мы можем видеть, хотя оба объекта имеют один и тот же метод fly , но теперь мы можем легко различить, какой из них реализует интерфейс Bird , а какой — интерфейс Airplane .

Мы увидели, как мы можем создавать свои собственные азбуки. Но часто не рекомендуется создавать собственные ABC и вместо этого использовать / подклассифицировать встроенные. В стандартной библиотеке Python есть много полезных ABC, которые мы можем легко использовать повторно. Мы можем получить список полезных встроенных ABC в модуле collections.abc — https://docs.python.org/3/library/collections.abc.html#module-collections.abc. Прежде чем писать свой собственный, проверьте, есть ли в стандартной библиотеке ABC для той же цели.

ABC и виртуальный подкласс

Мы также можем зарегистрировать класс как виртуальный подкласс ABC. В этом случае, даже если этот класс не является подклассом нашего ABC, он все равно будет рассматриваться как подкласс ABC (и, таким образом, будет принят как реализовавший интерфейс). Примеры кодов смогут лучше продемонстрировать это:

  @ Bird.register
класс Робин:
    проходить

r = Робин ()
  

А потом:

  >>> issubclass (Робин, Птица)
Правда
>>> isinstance (г, Птица)
Правда
>>>
  

В этом случае, даже если Robin не подклассифицирует нашу ABC или не определяет абстрактный метод, мы можем зарегистрировать как Bird . issubclass и isinstance , поведение можно перегрузить, добавив два соответствующих магических метода. Подробнее об этом читайте здесь — https://www.python.org/dev/peps/pep-3119/#overloading-isinstance-and-issubclass

.

Дополнительная литература

классов и интерфейсов Python — ThePythonGuru.com

  1. Дом
  2. Блог
  3. Классы и интерфейсы Python

(Спонсоры) Начните изучать Python с помощью DataCamp’s
бесплатный вводный курс по Python.Изучите науку о данных, выполняя интерактивные задания по кодированию и просматривая видео опытных инструкторов.
Начать сейчас!




 class SimpleGradebook:
    def __init __ (сам):
        self._grades = {}
    def add_student (я, имя):
        self._grades [имя] = []
    def report_grade (я, имя, оценка):
        себя._grades [имя] .append (оценка)

   def average_grade (я, имя):
        grades = self._grades [имя]
        сумма возврата (оценок) / лен (оценок)
 

 book = SimpleGradebook ()
book.add_student ('Исаак Ньютон')
book.report_grade ('Исаак Ньютон', 90)
book.report_grade ('Исаак Ньютон', 95)
book.report_grade ('Исаак Ньютон', 85)
 
 print (book.average_grade ('Isaac Newton'))
>>>
90.0
 

 из коллекций import defaultdict

класс BySubjectGradebook:
    def __init __ (сам):
        self._grades = {} # Внешний dict
    def add_student (я, имя):
        self._grades [name] = defaultdict (list) # Внутренний dict
 

 def report_grade (self, name, subject, grade):
       by_subject = self._grades [имя]
       grade_list = by_subject [тема]
       Grade_list.append (оценка)
    def average_grade (я, имя):
       by_subject = self._grades [имя]
       всего, count = 0, 0
       для оценок в by_subject.значения():
           итого + = сумма (оценки)
           count + = len (оценки)
       вернуть итог / количество
 

 book = BySubjectGradebook ()
book.add_student ('Альберт Эйнштейн')
book.report_grade ('Альберт Эйнштейн', 'Математика', 75)
book.report_grade ('Альберт Эйнштейн', 'Математика', 65)
book.report_grade ('Альберт Эйнштейн', 'Спортзал', 90)
book.report_grade ('Альберт Эйнштейн', 'Спортзал', 95)
 
 печать (кн.average_grade ('Альберт Эйнштейн'))
>>>
81,25
 

 класс WeightedGradebook:
    def __init __ (сам):
        себя._grades = {}
    def add_student (я, имя):
        self._grades [имя] = defaultdict (список)
    def report_grade (я, имя, тема, оценка, вес):
        by_subject = self._grades [имя]
        grade_list = by_subject [тема]
        grade_list.append ((оценка, вес))
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15 
 def average_grade (сам, имя):
        by_subject = сам._grades [имя]

        score_sum, score_count = 0, 0
        для предмета баллы в by_subject.items ():
            subject_avg, total_weight = 0, 0

        для оценки, вес в баллах:
            subject_avg + = оценка * вес
            total_weight + = вес

        score_sum + = subject_avg / total_weight
        score_count + = 1

    вернуть score_sum / score_count
 

 book = WeightedGradebook ()
книга.add_student ('Альберт Эйнштейн')
book.report_grade ('Альберт Эйнштейн', 'Математика', 75, 0,05)
book.report_grade ('Альберт Эйнштейн', 'Математика', 65, 0.15)
book.report_grade ('Альберт Эйнштейн', 'Математика', 70, 0.80)
book.report_grade ('Альберт Эйнштейн', 'Спортзал', 100, 0.40)
book.report_grade ('Альберт Эйнштейн', 'Спортзал', 85, 0.60)
print (book.average_grade ('Альберт Эйнштейн'))
>>>
80,25
 


 grades = []
grades.append ((95, 0.45))
grades.append ((85, 0.55))
итог = сумма (балл * вес для балла, вес в оценках)
total_weight = sum (вес для _, вес в оценках)
average_grade = total / total_weight
 

 grades = []
grades.append ((95, 0.45, 'Отличная работа'))
grades.append ((85, 0.55, 'В следующий раз лучше'))
итог = сумма (балл * вес для балла, вес, _ в оценках)
total_weight = сумма (вес для _, вес _ в оценках)
average_grade = total / total_weight
 

 из импорта коллекций namedtuple

Grade = namedtuple ('Оценка', ('оценка', 'вес'))
 


 класс Тема:
    def __init __ (сам):
        self._grades = []
    def report_grade (self, score, weight):
        self._grades.append (Оценка (балл, вес))
    def average_grade (сам):
        total, total_weight = 0, 0
        для оценки в self._grades:
            итого + = оценка. оценка * оценка. вес.
            total_weight + = grade.weight
        вернуть total / total_weight
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12 
 класс Студент:
    def __init __ (сам):
        себя._subjects = defaultdict (Тема)
    def get_subject (я, имя):
        вернуть self._subjects [имя]

    def average_grade (сам):
        всего, count = 0, 0
        для темы в self._subjects.values ​​():
            всего + = subject.average_grade ()
            count + = 1
        вернуть итог / количество
 

 журнал успеваемости класса:
    def __init __ (сам):
        себя._students = defaultdict (Студент)
    def get_student (я, имя):
        return self._students [имя]
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12 
 книга = Зачётная книжка ()
albert = book.get_student ('Альберт Эйнштейн')
математика = Альберт.get_subject ('Математика')
math.report_grade (75, 0,05)
math.report_grade (65, 0,15)
math.report_grade (70, 0.80)
gym = albert.get_subject ('Спортзал')
gym.report_grade (100, 0.40)
gym.report_grade (85, 0.60)
печать (albert.average_grade ())
>>>
80,25
 



 names = ['Сократ', 'Архимед', 'Платон', 'Аристотель']
names.sort (ключ = len)
печать (имена)
>>>
[Платон, Сократ, Аристотель, Архимед]
 

 def log_missing ():
    print ('Ключ добавлен')
    возврат 0
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17 
 из коллекций import defaultdict

current = {'зеленый': 12, 'синий': 3}
инкременты = [('красный'; 5),
('синий', 17),
('апельсин', 9),
]
результат = defaultdict (log_missing, текущий)
print ('До:', dict (результат))
для ключа - сумма с шагом:
результат [ключ] + = сумма
print ('После:', dict (результат))
>>>
Перед: {'зеленый': 12, 'синий': 3}
Ключ добавлен
Ключ добавлен
После: {'зеленый': 12, 'синий': 20, 'красный': 5, 'оранжевый': 9}
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12 
 def increment_with_report (текущий, приращения):
    added_count = 0

    def отсутствует ():
        nonlocal added_count # Закрытие с отслеживанием состояния
        добавленное_число + = 1
        возврат 0

    результат = defaultdict (отсутствует, текущий)
    для ключа - сумма с шагом:
        результат [ключ] + = сумма
    вернуть результат, added_count
 

 result, count = increment_with_report (current, приращения)
количество утверждений == 2
 

 class CountMissing:
    def __init __ (сам):
        себя.добавлено = 0
    def отсутствует (сам):
        self.added + = 1
        возврат 0
 

 counter = CountMissing ()
результат = defaultdict (counter.отсутствует, текущий) # Ссылка на метод
для ключа - сумма с шагом:
    результат [ключ] + = сумма
утверждать counter.added == 2
 

 class BetterCountMissing:
    def __init __ (сам):
        себя.добавлено = 0
    def __call __ (сам):
        self.added + = 1
        возврат 0

counter = BetterCountMissing ()
утверждать счетчик () == 0
assert callable (счетчик)
 

 counter = BetterCountMissing ()
result = defaultdict (counter, current) # Полагается на __call__
для ключа - сумма с шагом:
    результат [ключ] + = сумма
утверждать счетчик.добавлено == 2
 



 class InputData:
    def читать (сам):
        поднять NotImplementedError
 

 class PathInputData (InputData):
    def __init __ (сам, путь):
        супер().__в этом__()
        self.path = путь
    def читать (сам):
        с open (self.path) как f:
        вернуть f.read ()
 

 class Worker:
    def __init __ (self, input_data):
        себя.input_data = input_data
        self.result = Нет

    def map (self):
        поднять NotImplementedError

    def reduce (self, other):
        поднять NotImplementedError
 

 class LineCountWorker (Worker):
    def map (self):
        data = self.input_data.read ()
        self.result = данные.счетчик ('\ n')
    def reduce (self, other):
        self.result + = other.result
 

 import os

def generate_inputs (каталог_данных):
    для имени в os.listdir (data_dir):
        yield PathInputData (os.path.join (каталог_данных, имя))
 

 def create_workers (input_list):
    рабочие = []
    для input_data в input_list:
        рабочие.добавить (LineCountWorker (входные_данные))
    вернуть рабочих
 

 из потоков импорта Thread

def выполнить (рабочие):
    thread = [Thread (target = w.map) для w в воркерах]
    для резьбы в потоках: резьба.Начало()
    для потока в потоках: thread.join ()

    во-первых, * отдых = рабочие
    для работника в состоянии покоя:
        first.reduce (рабочий)
    return first.result
 

 def mapreduce (data_dir):
    input = generate_inputs (каталог_данных)
    worker = create_workers (входные данные)
    возврат выполнить (рабочие)
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17 
 импорт ОС
случайный импорт

def write_test_files (tmpdir):
    Операционные системы.македирс (tmpdir)
    для i в диапазоне (100):
        с open (os.path.join (tmpdir, str (i)), 'w') как f:
            f.write ('\ n' * random.randint (0, 100))

tmpdir = 'test_inputs'
write_test_files (tmpdir)

результат = mapreduce (tmpdir)
print (f'Есть {строки результата} ')

>>>
Всего 4360 линий
 

 class GenericInputData:
    def читать (сам):
        поднять NotImplementedError

    @classmethod
    def generate_inputs (cls, config):
        поднять NotImplementedError
 

 class PathInputData (GenericInputData):
    # ...
    @classmethod
    def generate_inputs (cls, config):
        data_dir = config ['каталог_данных']
        для имени в os.listdir (data_dir):
            yield cls (os.path.join (каталог_данных, имя))
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17 
 класс GenericWorker:
    def __init __ (self, input_data):
        self.input_data = input_data
        self.result = Нет

    def map (self):
        поднять NotImplementedError

    def reduce (self, other):
        поднять NotImplementedError

    @classmethod
    def create_workers (cls, input_class, config):
    рабочие = []
    для input_data в input_class.generate_inputs (конфигурация):
        worker.append (cls (входные_данные))
    вернуть рабочих
 

 class LineCountWorker (GenericWorker):
    ...
 

 def mapreduce (worker_class, input_class, config):
    рабочие = worker_class.create_workers (input_class, config)
    возврат выполнить (рабочие)
 

 config = {'data_dir': tmpdir}
результат = mapreduce (LineCountWorker, PathInputData, config)
print (f'Есть {строки результата} ')
>>>
Всего 4360 линий
 


 class MyBaseClass:
    def __init __ (self, value):
        себя.значение = значение


класс MyChildClass (MyBaseClass):
     def __init __ (сам):
        MyBaseClass .__ init __ (сам, 5)
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15 
 класс TimesTwo:
    def __init __ (сам):
        self.value * = 2

класс PlusFive:
    def __init __ (сам):
        self.value + = 5

Этот класс определяет свои родительские классы в одном порядке:

класс OneWay (MyBaseClass, TimesTwo, PlusFive):
    def __init __ (self, value):
        MyBaseClass .__ init __ (собственное значение, значение)
        TimesTwo.__само по себе)
        PlusFive .__ init __ (сам)
 

 foo = OneWay (5)
print ('Значение первого порядка (5 * 2) + 5 =', foo.value)
>>>
Значение первого заказа (5 * 2) + 5 = 15.
 

 class AnotherWay (MyBaseClass, PlusFive, TimesTwo):
    def __init __ (self, value):
        MyBaseClass.__init __ (себя, значение)
        TimesTwo .__ init __ (сам)
        PlusFive .__ init __ (сам)
 

 bar = AnotherWay (5)
print ('Второе значение для заказа', bar.значение)
>>>
Значение второго заказа - 15
 

 class TimesSeven (MyBaseClass):
    def __init __ (self, value):
        MyBaseClass.__init __ (себя, значение)
        self.value * = 7

класс PlusNine (MyBaseClass):
    def __init __ (self, value):
        MyBaseClass .__ init __ (собственное значение, значение)
        self.value + = 9
 

 class ThisWay (TimesSeven, PlusNine):
    def __init __ (self, value):
        TimesSeven .__ init __ (собственное значение, значение)
        PlusNine.__init __ (себя, значение)

foo = ThisWay (5)
print ('Должно быть (5 * 7) + 9 = 44, но есть', foo.value)
>>>
Должно быть (5 * 7) + 9 = 44, но это 14
 

 class TimesSevenCorrect (MyBaseClass):
    def __init __ (self, value):
        super () .__ init __ (значение)
        self.value * = 7

класс PlusNineCorrect (MyBaseClass):
    def __init __ (self, value):
        super () .__ init __ (значение)
        self.value + = 9
 

 class GoodWay (TimesSevenCorrect, PlusNineCorrect):
    def __init __ (self, value):
        super () .__ init __ (значение)

foo = GoodWay (5)
print ('Должно быть 7 * (5 + 9) = 98 и равно', foo.value)
>>>
Должно быть 7 * (5 + 9) = 98, а это 98
 

 mro_str = '\ n'.join (repr (cls) для cls в GoodWay.mro ())
печать (mro_str)

>>>
<класс '__main __. GoodWay'>
<класс '__main __. TimesSevenCorrect'>
<класс '__main __. PlusNineCorrect'>
<класс '__main__.MyBaseClass '>
<класс 'объект'>
 

 class ExplicitTrisect (MyBaseClass):
    def __init __ (self, value):
        super (ExplicitTrisect, self) .__ init __ (значение)
        self.value / = 3
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13 
 класс AutomaticTrisect (MyBaseClass):
    def __init __ (self, value):
        super (__ class__, self) .__ init __ (значение)
        self.value / = 3

класс ImplicitTrisect (MyBaseClass):
    def __init __ (self, value):
        супер().__init __ (значение)
        self.value / = 3

assert ExplicitTrisect (9) .value == 3
утверждать AutomaticTrisect (9) .value == 3
assert ImplicitTrisect (9) .value == 3
 



 class ToDictMixin:
    def to_dict (сам):
        вернуть себя._traverse_dict (сам .__ dict__)
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17 
 def _traverse_dict (self, instance_dict):
    output = {}
    для ключа значение в instance_dict.items ():
        output [ключ] = self._traverse (ключ, значение)
    возвратный вывод

def _traverse (self, key, value):
    если isinstance (значение, ToDictMixin):
        возвращаемое значение. to_dict ()
    elif isinstance (значение, dict):
        вернуть self._traverse_dict (значение)
    elif isinstance (значение, список):
        return [self._traverse (key, i) for i in value]
    elif hasattr (значение, '__dict__'):
        вернуть self._traverse_dict (значение .__ dict__)
    еще:
        возвращаемое значение
 

 class BinaryTree (ToDictMixin):
    def __init __ (self, value, left = None, right = None):
        себя.значение = значение
        self.left = left
        self.right = право
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12 
 дерево = BinaryTree (10,
    left = BinaryTree (7, right = BinaryTree (9)),
    right = BinaryTree (13, left = BinaryTree (11)))
печать (tree.to_dict ())
>>>
{'значение': 10,
'left': {'значение': 7,
        'left': нет,
        'right': {'значение': 9, 'left': Нет, 'right': Нет}},
'right': {'value': 13,
        'left': {'value': 11, 'left': Нет, 'right': Нет},
        'right': нет}}
 

 класс BinaryTreeWithParent (BinaryTree):
    def __init __ (self, value, left = None,
                right = None, parent = None):
        super () .__ init __ (значение, слева = слева, справа = справа)
        self.parent = родитель
 

 def _traverse (self, key, value):
        if (isinstance (значение, BinaryTreeWithParent) и
                key == 'parent'):
            return value.value # Предотвратить циклы
        еще:
            вернуть super ()._traverse (ключ, значение)
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16 
 корень = BinaryTreeWithParent (10)
root.left = BinaryTreeWithParent (7, родительский = корень)
root.left.right = BinaryTreeWithParent (9, parent = root.left)
печать (root.to_dict ())

>>>
{'значение': 10,
'left': {'значение': 7,
        'left': нет,
        'right': {'value': 9,
                'left': нет,
                'right': нет,
                'parent': 7},
        'parent': 10},
'right': нет,
'parent': Нет}
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14 
 класс NamedSubTree (ToDictMixin):
    def __init __ (я, имя, tree_with_parent):
        self.name = имя
        self.tree_with_parent = tree_with_parent

my_tree = NamedSubTree ('foobar', root.left.right)
print (my_tree.to_dict ()) # Нет бесконечного цикла

>>>
{'name': 'foobar',
'tree_with_parent': {'значение': 9,
                    'left': нет,
                    'right': нет,
                    'родитель': 7}}
 

 import json

класс JsonMixin:
    @classmethod
    def from_json (cls, data):
        kwargs = json.loads (данные)
        return cls (** kwargs)

    def to_json (сам):
        вернуть json.dumps (self.to_dict ())
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16 
 класс DatacenterRack (ToDictMixin, JsonMixin):
    def __init __ (self, switch = None, machines = None):
        себя.switch = переключатель (** переключатель)
        self.machines = [
            Машина (** kwargs) для kwargs в машинах]

класс Switch (ToDictMixin, JsonMixin):
    def __init __ (self, ports = None, speed = None):
        self.ports = порты
        self.speed = скорость

класс Machine (ToDictMixin, JsonMixin):
    def __init __ (self, cores = None, ram = None, disk = None):
        self.cores = ядра
        self.ram = баран
        self.disk = диск
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12 
 serialized = "" "{
    "switch": {"ports": 5, "speed": 1e9},
    "машины": [
        {"cores": 8, "ram": 32e9, "disk": 5e12},
        {"cores": 4, "ram": 16e9, "disk": 1e12},
        {"cores": 2, "ram": 4e9, "disk": 500e9}
    ]
} "" "

deserialized = DatacenterRack.from_json (сериализованный)
roundtrip = десериализованный.to_json ()
assert json.loads (сериализованный) == json.loads (туда и обратно)
 



 class MyObject:
    def __init __ (сам):
        себя.public_field = 5
        self .__ private_field = 10

    def get_private_field (сам):
        вернуть self .__ private_field
 

 foo = MyObject ()
утверждать foo.public_field == 5
 

 foo .__ private_field

>>>
Проследить ...
AttributeError: объект 'MyObject' не имеет атрибута '__private_field'
 

 class MyOtherObject:
    def __init __ (сам):
        себя.__private_field = 71

    @classmethod
    def get_private_field_of_instance (cls, экземпляр):
        вернуть экземпляр .__ private_field

bar = MyOtherObject ()
assert MyOtherObject.get_private_field_of_instance (bar) == 71
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14 
 класс MyParentObject:
    def __init __ (сам):
        себя.__private_field = 71

класс MyChildObject (MyParentObject):
    def get_private_field (сам):
        вернуть self .__ private_field

baz = MyChildObject ()
baz.get_private_field ()

>>>
Проследить ...
AttributeError: объект MyChildObject не имеет атрибута _MyChildObject__private_field
 

 print (baz .__ dict__)

>>>
{'_MyParentObject__private_field': 71}
 

 class MyStringClass:
    def __init __ (self, value):
        себя.__value = значение

    def get_value (сам):
        return str (значение self .__)

foo = MyStringClass (5)
assert foo.get_value () == '5'
 

 class MyIntegerSubclass (MyStringClass):
    def get_value (сам):
        вернуть int (self._MyStringClass__value)

foo = MyIntegerSubclass ('5')
assert foo.get_value () == 5
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14 
 класс MyBaseClass:
    def __init __ (self, value):
        себя.__value = значение

    def get_value (сам):
        вернуть значение self .__

класс MyStringClass (MyBaseClass):
    def get_value (сам):
        return str (super (). get_value ()) # Обновлено

класс MyIntegerSubclass (MyStringClass):
    def get_value (сам):
        return int (self._MyStringClass__value) # Не обновляется
 

 foo = MyIntegerSubclass (5)
foo.get_value ()

>>>
Проследить ...
AttributeError: объект MyIntegerSubclass не имеет атрибута _MyStringClass__value
 

 class MyStringClass:
    def __init __ (self, value):
        # Сохраняет введенное пользователем значение для объекта.
        # Он должен быть приведен к строке. После назначения в
        # объект, который следует рассматривать как неизменяемый.
        self._value = значение
    ...
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17 
 класс ApiClass:
    def __init __ (сам):
        self._value = 5

    def get (self):
        вернуть self._value

класс Child (ApiClass):
    def __init __ (сам):
        супер () .__ init __ ()
        self._value = 'hello' # конфликты

a = Ребенок ()
print (f '{a.get ()} и {a._value} должны быть разными')

>>>
привет и привет должны быть разными
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17 
 класс ApiClass:
    def __init __ (сам):
        self .__ value = 5 # Двойное подчеркивание

    def get (self):
        вернуть себя.__value # Двойное подчеркивание

класс Child (ApiClass):
    def __init __ (сам):
        супер () .__ init __ ()
        self._value = 'hello' # ОК!

a = Ребенок ()
print (f '{a.get ()} и {a._value} разные')

>>>
5 и привет разные
 



 класс FrequencyList (список):
    def __init __ (я, члены):
        супер().__init __ (участники)

    частота def (self):
        counts = {}
        для элемента в себе:
            counts [item] = counts.get (item, 0) + 1
        счетчики возврата
 

 foo = FrequencyList (['a', 'b', 'a', 'c', 'b', 'a', ' d '])
print ('Длина', len (foo))

фу.поп ()
print ('После pop:', repr (foo))
print ('Частота:', foo.frequency ())

>>>
Длина 7
После pop: ['a', 'b', 'a', 'c', 'b', 'a']
Частота: {'a': 3, 'b': 2, 'c': 1}
 

 класс BinaryNode:
    def __init __ (self, value, left = None, right = None):
        себя.значение = значение
        self.left = left
        self.right = право
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13 
 класс IndexableNode (BinaryNode):
    def _traverse (сам):
        если сам.слева не нет:
            выход из self.left._traverse ()
        сдаться
        если self.right не равно None:
            выход из self.right._traverse ()

    def __getitem __ (self, index):
        для i, элемент в перечислении (self._traverse ()):
            если i == index:
                вернуть item.value
        поднять IndexError (f'Index {index} вне допустимого диапазона ')
 

 tree = IndexableNode (
    10,
    left = IndexableNode (
            5,
            left = IndexableNode (2),
            right = IndexableNode (
                6,
                right = IndexableNode (7))),
    right = IndexableNode (
    15,
    left = IndexableNode (11)))
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14 
 print ('LRR is', tree.left.right.right.value)
print ('Индекс 0 равен', tree [0])
print ('Индекс 1 равен', tree [1])
print ('11 в дереве?', 11 в дереве)
print ('17 в дереве? ', 17 в дереве)
print ('Дерево есть', список (дерево))

>>>
LRR - 7
Индекс 0 равен 2
Индекс 1 равен 5
11 в дереве? Правда
17 в дереве? Ложь
Дерево [2, 5, 6, 7, 10, 11, 15]
 

 len (tree)

>>>
Проследить ...
TypeError: объект типа IndexableNode не имеет len ()
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21 год
22
23 
 класс SequenceNode (IndexableNode):
    def __len __ (сам):
        для count _ в enumerate (self._traverse (), 1):
            проходить
        счетчик возврата

tree = SequenceNode (
    10,
    left = SequenceNode (
        5,
        left = SequenceNode (2),
        справа = SequenceNode (
            6,
            right = SequenceNode (7))),
    справа = SequenceNode (
    15,
    left = SequenceNode (11))
)

print ('Длина дерева', len (дерево))

>>>
Длина дерева 7
 

 из коллекций.Последовательность импорта abc
класс BadType (Последовательность):
    проходить

foo = BadType ()

>>>
Проследить ...
TypeError: невозможно создать экземпляр абстрактного класса BadType с помощью абстрактных методов __getitem__, __len__
 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21 год
22 
 класс BetterNode (SequenceNode, Sequence):
    проходить

tree = BetterNode (
    10,
    left = BetterNode (
        5,
        left = BetterNode (2),
        right = BetterNode (
            6,
            right = BetterNode (7))),
    right = BetterNode (
        15,
        left = BetterNode (11))
    )

print ('Индекс 7 есть', tree.индекс (7))
print ('Число 10 равно', tree.count (10))

>>>
Индекс 7 равен 3
Счетчик 10 равен 1
 




PEP: 245
Название: Синтаксис интерфейса Python
Автор: Мишель Пеллетье <мишель у пользователей.sourceforge.net>
Обсуждения — Кому: http://www.zope.org/Wikis/Interfaces
Статус: Отклонено
Тип: Standards Track
Создано: 11.01.2001
Версия Python: 2.2
Пост-история: 21 марта 2001 г.

Интерфейс Python Абстрактный класс Python
Интерфейс — это набор методов и атрибутов этого объекта. Мы можем использовать абстрактный базовый класс для определения и обеспечения интерфейса.
Все методы интерфейса абстрактные Абстрактный класс может иметь абстрактные методы, а также конкретные методы.
Мы используем интерфейс, если все функции должны быть реализованы по-разному для разных объектов. Абстрактные классы используются, когда есть некоторая общая черта, общая для всех объектов как таковых.
Интерфейс медленный по сравнению с абстрактным классом. Абстрактные классы быстрее.