Интерфейс 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 out → Lay 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
- Дом
- Блог
- Классы и интерфейсы Python
(Спонсоры) Начните изучать Python с помощью DataCamp’s
бесплатный вводный курс по Python.Изучите науку о данных, выполняя интерактивные задания по кодированию и просматривая видео опытных инструкторов.
Начать сейчас!
Обновлено 7 января 2020 г.
Как объектно-ориентированный язык программирования Python поддерживает полный набор функций, таких как наследование, полиморфизм и инкапсуляция. Для выполнения задач на Python часто требуется написание новых классов и определение того, как они взаимодействуют через свои интерфейсы и иерархии.
Классы и наследование Python позволяют легко выразить предполагаемое поведение программы с помощью объектов. Они позволяют со временем улучшать и расширять функциональность. Они обеспечивают гибкость в условиях меняющихся требований. Знание того, как их правильно использовать, позволяет вам писать поддерживаемый код.
Правило 37: Составление классов вместо вложения многих уровней встроенных типов
Тип встроенного словаря 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 |
Словари и связанные с ними встроенные типы настолько просты в использовании, что существует опасность их чрезмерного расширения для написания нестабильного кода. Например, предположим, что я хочу расширить класс SimpleGradebook, чтобы сохранить список оценок по предметам, а не только в целом. Я могу сделать это, изменив словарь _grades
, чтобы сопоставить имена учащихся (его ключи) с еще одним словарем (его значениями). Самый внутренний словарь будет сопоставлять предметы (его ключи) со списком оценок (его значениями).Здесь я делаю это, используя экземпляр defaultdict
для внутреннего словаря для обработки отсутствующих предметов (см. Правило 17: «Предпочитайте defaultdict над setdefault для обработки пропущенных».
ing Items in Internal State »для фона):
из коллекций import defaultdict класс BySubjectGradebook: def __init __ (сам): self._grades = {} # Внешний dict def add_student (я, имя): self._grades [name] = defaultdict (list) # Внутренний dict |
Это кажется достаточно простым.Методы report_grade
и average_grade
немного усложняют работу с многоуровневым словарем, но, по-видимому, управляемы:
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 ((оценка, вес)) |
Хотя изменения в report_grade
кажутся простыми — просто сделайте экземпляры кортежа в списке оценок — метод average_grade
теперь имеет цикл внутри цикла и его трудно читать:
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 |
Когда вы видите подобную сложность, пора перейти от встроенных типов, таких как словари, кортежи, наборы и списки, к иерархии
классы.
В примере с оценками сначала я не знал, что мне нужно поддерживать взвешенные оценки, поэтому сложность создания классов казалась необоснованной. Встроенный в Python словарь и типы кортежей упростили работу, добавляя слой за слоем во внутреннюю бухгалтерию. Но вам следует избегать этого для более чем одного уровня вложенности; Использование словарей, которые содержат словари, затрудняет чтение вашего кода другими программистами и настраивает вас на кошмар обслуживания.
Как только вы поймете, что ваша бухгалтерия усложняется, разбейте все на классы.Затем вы можете предоставить четко определенные интерфейсы, которые лучше инкапсулируют ваши данные. Этот подход также позволяет вам создать уровень абстракции между вашими интерфейсами и вашими конкретными реализациями.
Рефакторинг до классов
Есть много подходов к рефакторингу (см. Правило 89: «Учитывайте предупреждения о рефакторинге и переносе использования»). В этом случае
я могу начать переходить к классам в нижней части дерева зависимостей: одна оценка. Класс кажется слишком тяжелым для такой простой информации.Однако кортеж кажется подходящим, потому что оценки неизменяемы. Здесь я использую кортеж (оценка, вес) для отслеживания оценок в списке:
grades = [] grades.append ((95, 0.45)) grades.append ((85, 0.55)) итог = сумма (балл * вес для балла, вес в оценках) total_weight = sum (вес для _, вес в оценках) average_grade = total / total_weight |
Я использовал _
(имя переменной подчеркивания, соглашение Python для неиспользуемых переменных), чтобы захватить первую запись в кортеже каждого класса и проигнорировать ее при вычислении total_weight
.
Проблема с этим кодом в том, что экземпляры кортежа позиционны. Например, если я хочу связать дополнительную информацию с оценкой, например, набор заметок от учителя, мне нужно переписать каждое использование кортежа из двух элементов, чтобы знать, что теперь вместо него присутствуют три элемента.
из двух, что означает, что мне нужно использовать _
, чтобы игнорировать определенные индексы:
grades = [] grades.append ((95, 0.45, 'Отличная работа')) grades.append ((85, 0.55, 'В следующий раз лучше')) итог = сумма (балл * вес для балла, вес, _ в оценках) total_weight = сумма (вес для _, вес _ в оценках) average_grade = total / total_weight |
Этот шаблон расширения кортежей все длиннее и длиннее аналогичен расширению слоев словарей.Как только вы обнаружите, что используете более двух кортежей, самое время подумать о другом подходе.
Тип namedtuple
во встроенном модуле коллекций делает именно то, что мне нужно в этом случае: он позволяет мне легко определять крошечные неизменяемые классы данных:
из импорта коллекций namedtuple Grade = namedtuple ('Оценка', ('оценка', 'вес')) |
Эти классы могут быть созданы с помощью позиционных аргументов или аргументов ключевого слова.Поля доступны с именованными атрибутами. Именованные атрибуты позволяют легко перейти от namedtuple
к классу позже, если требования снова изменятся, и мне нужно, скажем, поддерживать изменчивость или поведение в простых контейнерах данных.
Ограничения namedtuple
Хотя namedtuple
полезен во многих случаях, важно понимать, когда он может принести больше вреда, чем пользы:
Вы не можете указать значения аргументов по умолчанию для
классов namedtuple
.Это делает их громоздкими, когда ваши данные могут иметь много необязательных свойств. Если вы обнаружите, что используете больше, чем несколько атрибутов, использование встроенного модуля классов данных может быть лучшим выбором.Значения атрибутов экземпляров namedtuple по-прежнему доступны с использованием числовых индексов и итерации. Это может привести к непреднамеренному использованию, особенно во внешних API-интерфейсах, что затруднит последующий переход к реальному классу. Если вы не контролируете все использование экземпляров
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 |
Также можно было бы написать обратно-совместимые методы, которые помогут перенести использование старого стиля API в новую иерархию объектов.
Что следует помнить
- Избегайте создания словарей со значениями, которые являются словарями, длинными кортежами или сложными вложениями других встроенных типов.
- Используйте
namedtuple
для облегченных неизменяемых контейнеров данных, прежде чем вам понадобится гибкость полного класса. - Переместите свой бухгалтерский код на использование нескольких классов, когда ваши внутренние словари состояний усложняются.
Элемент 38: Функции принятия вместо классов для простых интерфейсов
Многие встроенные API Python позволяют настраивать поведение, передавая функцию. Эти хуки используются API для обратного вызова вашего кода во время выполнения.Например, метод сортировки типа списка принимает необязательный ключевой аргумент, который используется для определения значения каждого индекса для сортировки (подробности см. В Правиле 14: «Сортировка по сложным критериям с использованием ключевого параметра»). Здесь я сортирую список имен по их длине, предоставляя встроенную функцию len в качестве ключевой ловушки:
names = ['Сократ', 'Архимед', 'Платон', 'Аристотель'] names.sort (ключ = len) печать (имена) >>> [Платон, Сократ, Аристотель, Архимед] |
В других языках можно ожидать, что хуки будут определены абстрактным классом.В Python многие хуки — это просто функции без сохранения состояния с четко определенными аргументами и возвращаемыми значениями. Функции идеальны для ловушек, потому что их проще описывать и определять, чем классы. Функции работают как перехватчики, потому что Python имеет первоклассные функции: функции и методы можно передавать и ссылаться на них, как на любое другое значение в языке. Например, предположим, что я хочу настроить поведение класса defaultdict
(см. Правило 17: «Предпочитайте defaultdict
чем setdefault
для
Обработка отсутствующих элементов во внутреннем состоянии »для фона).Эта структура данных позволяет вам предоставить функцию, которая будет вызываться без аргументов при каждом доступе к отсутствующему ключу. Функция должна возвращать значение по умолчанию, которое недостающий ключ должен иметь в словаре. Здесь я определяю ловушку, которая регистрирует каждый раз, когда ключ отсутствует, и возвращает 0 для значения по умолчанию:
def log_missing (): print ('Ключ добавлен') возврат 0 |
Учитывая начальный словарь и набор желаемых приращений, я могу запустить функцию log_missing и напечатать дважды (для «красного» и «оранжевого»):
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} |
Использование таких функций, как log_missing, упрощает создание и тестирование API, поскольку оно отделяет побочные эффекты от детерминированного поведения.Например, предположим, что теперь я хочу, чтобы обработчик значения по умолчанию был передан в defaultdict
, чтобы подсчитать общее количество отсутствующих ключей. Один из способов добиться этого — использовать закрытие с отслеживанием состояния (подробности см. В рекомендации 21: «Узнайте, как замыкания взаимодействуют с областью действия переменной»). Здесь я определяю вспомогательную функцию, которая использует такое закрытие как обработчик значения по умолчанию:
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 |
Выполнение этой функции дает ожидаемый результат (2), даже несмотря на то, что defaultdict
не знает, что недостающий обработчик поддерживает состояние.Еще одно преимущество принятия простых функций для интерфейсов заключается в том, что позже можно легко добавить функциональность, скрыв состояние в закрытии:
result, count = increment_with_report (current, приращения) количество утверждений == 2 |
Проблема с определением закрытия для обработчиков с отслеживанием состояния заключается в том, что его труднее читать, чем в примере с функцией без сохранения состояния. Другой подход — определить небольшой класс, который инкапсулирует состояние, которое вы хотите отслеживать:
class CountMissing: def __init __ (сам): себя.добавлено = 0 def отсутствует (сам): self.added + = 1 возврат 0 |
На других языках можно ожидать, что теперь defaultdict
нужно будет изменить, чтобы приспособить интерфейс CountMissing
. Но в Python, благодаря функциям первого класса, вы можете ссылаться на метод CountMissing.missing
непосредственно на объекте и передать его defaultdict
в качестве обработчика значения по умолчанию. Тривиально обеспечить, чтобы метод экземпляра объекта удовлетворял интерфейсу функции:
counter = CountMissing () результат = defaultdict (counter.отсутствует, текущий) # Ссылка на метод для ключа - сумма с шагом: результат [ключ] + = сумма утверждать counter.added == 2 |
Использование вспомогательного класса, подобного этому, для обеспечения поведения закрытия с отслеживанием состояния более наглядно, чем использование функции increment_with_report, как указано выше. Однако по отдельности все еще не сразу очевидно, какова цель класса CountMissing. Кто создает объект CountMissing
? Кто называет недостающий метод? Потребуется ли добавление в класс других общедоступных методов в будущем? Пока вы не увидите его использование с defaultdict
, этот класс остается загадкой.
Чтобы прояснить эту ситуацию, Python позволяет классам определять специальный метод __call__
. __call__
позволяет вызывать объект как функцию. Это также заставляет вызываемую встроенную функцию возвращать True для такого экземпляра, как обычная функция или метод. Все объекты
которые могут быть выполнены таким образом, называются вызываемыми объектами :
class BetterCountMissing: def __init __ (сам): себя.добавлено = 0 def __call __ (сам): self.added + = 1 возврат 0 counter = BetterCountMissing () утверждать счетчик () == 0 assert callable (счетчик) |
Здесь я использую экземпляр BetterCountMissing в качестве обработчика значения по умолчанию для defaultdict
, чтобы отслеживать количество добавленных отсутствующих ключей:
counter = BetterCountMissing () result = defaultdict (counter, current) # Полагается на __call__ для ключа - сумма с шагом: результат [ключ] + = сумма утверждать счетчик.добавлено == 2 |
Это намного яснее, чем пример CountMissing.missing. Метод __call__
указывает, что экземпляры класса будут использоваться там, где аргумент функции также будет подходящим (например, хуки API). Он направляет новых читателей кода к точке входа, которая отвечает за основное поведение класса. Он дает четкий намек на то, что цель класса — действовать как закрытие с отслеживанием состояния. Лучше всего то, что defaultdict
все еще не видит, что происходит, когда
вы используете __call__
.Все, что требуется для defaultdict
, — это функция для ловушки значения по умолчанию. Python предоставляет множество различных способов удовлетворить простой интерфейс функций, и вы можете выбрать тот, который лучше всего подходит для ваших задач.
Что следует помнить
- Вместо определения и создания экземпляров классов вы часто можете просто использовать функции для простых интерфейсов между компонентами в Python.
- Ссылки на функции и методы в Python являются первоклассными, что означает, что они могут использоваться в выражениях (как и любой другой тип).
- Специальный метод
__call__
позволяет вызывать экземпляры класса как обычные функции Python. - Если вам нужна функция для поддержания состояния, рассмотрите возможность определения класса, который предоставляет метод
__call__
вместо определения закрытия с отслеживанием состояния.
Правило 39: Используйте полиморфизм @classmethod для общего конструирования объектов
В Python не только объекты поддерживают полиморфизм, но и классы. Что это значит и для чего это нужно? Полиморфизм позволяет нескольким классам в иерархии реализовывать свои собственные уникальные версии метода.Это означает, что многие классы могут выполнять один и тот же интерфейс или абстрактный базовый класс, обеспечивая при этом разные функциональные возможности (см. Правило 43: «Наследование от collections.abc для пользовательских типов контейнеров»).
Например, предположим, что я пишу реализацию MapReduce и хочу, чтобы общий класс представлял входные данные. Здесь я определяю такой класс с методом чтения, который должен определяться подклассами:
class InputData: def читать (сам): поднять NotImplementedError |
У меня также есть конкретный подкласс InputData, который считывает данные из файла на диске:
class PathInputData (InputData): def __init __ (сам, путь): супер().__в этом__() self.path = путь def читать (сам): с open (self.path) как f: вернуть f.read () |
Я мог бы иметь любое количество подклассов InputData
, таких как PathInputData, и каждый из них мог бы реализовать стандартный интерфейс для read
, чтобы вернуть данные для обработки. Другие подклассы InputData
могут читать из сети, прозрачно распаковывать данные и так далее.
Мне нужен аналогичный абстрактный интерфейс для MapReduce
worker, который потребляет входные данные стандартным образом:
class Worker: def __init __ (self, input_data): себя.input_data = input_data self.result = Нет def map (self): поднять NotImplementedError def reduce (self, other): поднять NotImplementedError |
Здесь я определяю конкретный подкласс Worker для реализации конкретной функции MapReduce
, которую я хочу применить — простой счетчик новой строки:
class LineCountWorker (Worker): def map (self): data = self.input_data.read () self.result = данные.счетчик ('\ n') def reduce (self, other): self.result + = other.result |
Может показаться, что эта реализация идет отлично, но я достиг самого большого препятствия во всем этом. Что связывает все эти части? У меня есть хороший набор классов с разумными интерфейсами и абстракциями, но он полезен только после создания объектов. Что отвечает за создание объектов и организацию MapReduce
?
Самый простой подход — вручную построить и связать объекты с некоторыми вспомогательными функциями.Здесь я перечисляю содержимое каталога и создаю экземпляр PathInputData
для каждого содержащегося в нем файла:
import os def generate_inputs (каталог_данных): для имени в os.listdir (data_dir): yield PathInputData (os.path.join (каталог_данных, имя)) |
Затем я создаю экземпляры LineCountWorker
, используя экземпляры InputData
, возвращаемые generate_inputs
:
def create_workers (input_list): рабочие = [] для input_data в input_list: рабочие.добавить (LineCountWorker (входные_данные)) вернуть рабочих |
Я выполняю эти экземпляры Worker, распределяя шаг карты по нескольким потокам (см. Правило 53: «Используйте потоки для блокировки ввода-вывода, избегайте параллелизма» для справки). Затем я повторно вызываю сокращение, чтобы объединить результаты в одно окончательное значение:
из потоков импорта 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 линий |
В чем проблема? Огромная проблема заключается в том, что функция mapreduce
вообще не является универсальной. Если бы я хотел написать еще один подкласс InputData
или Worker
, мне также пришлось бы переписать функции generate_inputs
, create_workers
и mapreduce
, чтобы они соответствовали друг другу.
Эта проблема сводится к необходимости универсального способа создания объектов. На других языках эту проблему можно решить с помощью полиморфизма конструктора, требующего, чтобы каждый подкласс InputData
предоставлял специальный конструктор, который может использоваться в общем случае вспомогательными методами, которые оркестрируют MapReduce
(аналогично фабричному шаблону). Проблема в том, что Python допускает только один метод конструктора __init__
. Неразумно требовать, чтобы каждый подкласс InputData
имел совместимый конструктор.
Лучший способ решить эту проблему — использовать полиморфизм метода классов. Это в точности похоже на полиморфизм метода экземпляра, который я использовал для InputData.read
, за исключением того, что он предназначен для целых классов, а не для их созданных объектов.
Позвольте мне применить эту идею к классам MapReduce
. Здесь я расширяю класс InputData
с помощью общего @classmethod
, который отвечает за создание новых экземпляров InputData
с использованием общего интерфейса:
class GenericInputData: def читать (сам): поднять NotImplementedError @classmethod def generate_inputs (cls, config): поднять NotImplementedError |
У меня есть generate_inputs
, взять словарь с набором параметров конфигурации, которые конкретный подкласс GenericInputData
должен интерпретировать.Здесь я использую конфигурацию, чтобы найти каталог для вывода входных файлов:
class PathInputData (GenericInputData): # ... @classmethod def generate_inputs (cls, config): data_dir = config ['каталог_данных'] для имени в os.listdir (data_dir): yield cls (os.path.join (каталог_данных, имя)) |
Точно так же я могу сделать create_workers
вспомогательной частью класса GenericWorker
. Здесь я использую параметр input_class
, который должен быть подклассом GenericInputData
, для генерации необходимых входных данных.Я создаю экземпляры конкретного подкласса GenericWorker
, используя cls ()
в качестве универсального конструктора:
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 (входные_данные)) вернуть рабочих |
Обратите внимание, что вызов input_class.generate_inputs
выше является полиморфизмом классов, который я пытаюсь показать. Вы также можете увидеть, как create_workers, вызывающий cls ()
, предоставляет альтернативный способ создания объектов GenericWorker
помимо прямого использования метода __init__
.
Влияние на мой конкретный подкласс GenericWorker
— это не что иное, как изменение его родительского класса:
class LineCountWorker (GenericWorker): ... |
Наконец, я могу переписать функцию mapreduce
, чтобы она была полностью универсальной, вызвав create_workers
:
def mapreduce (worker_class, input_class, config): рабочие = worker_class.create_workers (input_class, config) возврат выполнить (рабочие) |
Запуск нового рабочего процесса на наборе тестовых файлов дает тот же результат, что и старая реализация. Разница в том, что для функции mapreduce
требуется больше параметров, чтобы она могла работать в общем виде:
config = {'data_dir': tmpdir} результат = mapreduce (LineCountWorker, PathInputData, config) print (f'Есть {строки результата} ') >>> Всего 4360 линий |
Теперь я могу писать другие подклассы GenericInputData
и GenericWorker
по своему желанию, без необходимости переписывать какой-либо связующий код.
Что следует помнить
* Python поддерживает только один конструктор для каждого класса: метод __init__
.
* Используйте @classmethod
для определения альтернативных конструкторов для ваших классов.
* Используйте полиморфизм метода класса, чтобы предоставить общие способы построения и соединения множества конкретных подклассов.
Элемент 40: Инициализировать родительские классы с помощью super
Старый простой способ инициализировать родительский класс из дочернего класса — это напрямую вызвать метод __init__
родительского класса с дочерним экземпляром:
class MyBaseClass: def __init __ (self, value): себя.значение = значение класс MyChildClass (MyBaseClass): def __init __ (сам): MyBaseClass .__ init __ (сам, 5) |
Этот подход отлично работает для базовых иерархий классов, но во многих случаях дает сбой.
Если на класс действует множественное наследование (чего следует избегать в целом; см. Рекомендацию 41: «Рассмотрите возможность создания функциональных возможностей с помощью смешанных классов»), прямой вызов методов суперкласса __init__
может привести к непредсказуемому поведению.
Одна проблема заключается в том, что порядок вызова __init__
не указан для всех подклассов.Например, здесь я определяю два родительских класса, которые работают с полем значения экземпляра:
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. |
Вот еще один класс, который определяет те же родительские классы, но в другом порядке (PlusFive, за которым следует TimesTwo, а не наоборот):
class AnotherWay (MyBaseClass, PlusFive, TimesTwo): def __init __ (self, value): MyBaseClass.__init __ (себя, значение) TimesTwo .__ init __ (сам) PlusFive .__ init __ (сам) |
Однако я оставил вызовы конструкторов родительского класса — PlusFive .__ init__
и TimesTwo .__ init__
— в том же порядке, что и раньше, что означает, что поведение этого класса не соответствует порядку родительских классов в его определении. Конфликт между наследством
базовые классы и вызовы __init__
трудно обнаружить, что делает это особенно трудным для понимания новыми читателями кода:
bar = AnotherWay (5) print ('Второе значение для заказа', bar.значение) >>> Значение второго заказа - 15 |
Другая проблема возникает при наследовании алмаза. Алмазное наследование происходит, когда подкласс наследуется от двух отдельных классов, которые имеют один и тот же суперкласс где-то в иерархии. Алмазное наследование приводит к многократному запуску метода общего суперкласса __init__
, вызывая неожиданное поведение. Например, здесь я определяю два дочерних класса, которые наследуются от MyBaseClass
:
class TimesSeven (MyBaseClass): def __init __ (self, value): MyBaseClass.__init __ (себя, значение) self.value * = 7 класс PlusNine (MyBaseClass): def __init __ (self, value): MyBaseClass .__ init __ (собственное значение, значение) self.value + = 9 |
Затем я определяю дочерний класс, который наследуется от обоих этих классов, делая MyBaseClass
вершиной ромба:
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 |
Вызов конструктора второго родительского класса, PlusNine .__ init__
, приводит к сбросу self.value
обратно на 5
, когда MyBaseClass .__ init__
вызывается второй раз. Это приводит к вычислению self.value
как 5 + 9 = 14
, полностью игнорируя эффект TimesSeven.__init__
конструктор. Такое поведение удивительно, и его может быть очень сложно отладить в более сложных случаях.
Для решения этих проблем в Python есть супер-встроенная функция и стандартный порядок разрешения методов (MRO). super гарантирует, что общие суперклассы в алмазных иерархиях запускаются только один раз (другой пример см. в Правиле 48: «Проверка подклассов с помощью init_subclass »). MRO определяет порядок, в котором инициализируются суперклассы, следуя алгоритму, называемому линеаризацией C3.
Здесь я снова создаю иерархию классов в форме ромба, но на этот раз я использую super для инициализации родительского класса:
class TimesSevenCorrect (MyBaseClass): def __init __ (self, value): super () .__ init __ (значение) self.value * = 7 класс PlusNineCorrect (MyBaseClass): def __init __ (self, value): super () .__ init __ (значение) self.value + = 9 |
Теперь верхняя часть ромба, MyBaseClass.__init__
, запускается только один раз. Другие родительские классы запускаются в порядке, указанном в инструкции class
:
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 |
Поначалу этот порядок может показаться обратным. Не должно быть TimesSevenCorrect.__init__
запустился первым? Разве результат не должен быть (5 * 7) + 9 = 44
? Ответ — нет. Этот порядок соответствует тому, что
MRO определяет для этого класса. Заказ ТОиР доступен по классу
метод называется mro
:
mro_str = '\ n'.join (repr (cls) для cls в GoodWay.mro ()) печать (mro_str) >>> <класс '__main __. GoodWay'> <класс '__main __. TimesSevenCorrect'> <класс '__main __. PlusNineCorrect'> <класс '__main__.MyBaseClass '> <класс 'объект'> |
Когда я вызываю GoodWay (5), он, в свою очередь, вызывает TimesSevenCorrect .__ init__
, который вызывает PlusNineCorrect .__ init__
, который вызывает MyBaseClass .__ init__
. Когда это достигает вершины ромба, все методы инициализации фактически выполняют свою работу в порядке, обратном тому, в котором были вызваны их функции __init__
. MyBaseClass .__ init__
присваивает значение 5. PlusNineCorrect.__init__
добавляет 9, чтобы сделать значение равным 14. TimesSevenCorrect .__ init__
умножает его на 7, чтобы сделать значение равным 98.
Помимо обеспечения устойчивости множественного наследования, вызов super () .__ init__
также более удобен в обслуживании, чем вызов MyBaseClass .__ init__
непосредственно из подклассов. Позже я мог бы переименовать MyBaseClass
во что-то другое или унаследовать TimesSevenCorrect
и PlusNineCorrect
от другого суперкласса без необходимости обновлять их методы __init__
для соответствия.
Суперфункция также может быть вызвана с двумя параметрами: сначала тип класса, чье родительское представление MRO вы пытаетесь получить доступ, а затем экземпляр, к которому нужно получить доступ к этому представлению. Использование этих необязательных параметров в конструкторе выглядит следующим образом:
class ExplicitTrisect (MyBaseClass): def __init __ (self, value): super (ExplicitTrisect, self) .__ init __ (значение) self.value / = 3 |
Однако эти параметры не требуются для инициализации экземпляра объекта.Компилятор Python автоматически предоставляет вам правильные параметры ( __class__
и self
), когда super вызывается с нулевыми аргументами в определении класса. Это означает, что все три варианта использования эквивалентны:
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 |
Единственный раз, когда вы должны предоставить параметры super, — это ситуации, когда вам нужно получить доступ к конкретным функциям реализации суперкласса из дочернего класса (например, чтобы обернуть или повторно использовать
функциональность).
Что следует помнить
- Стандартный порядок разрешения методов (MRO) Python решает проблемы порядка инициализации суперкласса и наследования алмаза.
- Используйте встроенную суперфункцию с нулевыми аргументами для инициализации родительских классов.
Правило 41: Рассмотрение функциональных возможностей компоновки с добавлением классов
Python — это объектно-ориентированный язык со встроенными средствами для обеспечения возможности управления множественным наследованием (см. Правило 40: «Инициализируйте родительские классы с помощью super»). Однако лучше избегать множественного наследования.
все вместе.
Если вам нужно удобство и инкапсуляция, присущие множественному наследованию, но при этом избежать потенциальных головных болей, подумайте о написании микширования вместо этого.Подмешивание — это класс, который определяет только небольшой набор дополнительных методов для своих дочерних классов.
предоставлять. Классы смешивания не определяют свои собственные атрибуты экземпляра и не требуют вызова конструктора __init__
.
Писать микшеры легко, потому что Python упрощает проверку текущего состояния любого объекта, независимо от его типа. Динамическая проверка означает, что вы можете написать универсальную функциональность только один раз в смешанном виде, а затем ее можно будет применить ко многим другим классам.Добавления могут быть составлены и распределены по слоям, чтобы минимизировать повторяющийся код и максимально увеличить повторное использование.
Например, скажем, мне нужна возможность преобразовать объект Python из его представления в памяти в словарь, готовый для сериализации. Почему бы не написать эту функцию в общем, чтобы я мог использовать ее с
все мои занятия?
Здесь я определяю пример микширования, который выполняет это с помощью нового общедоступного метода, который добавляется к любому наследующему от него классу:
class ToDictMixin: def to_dict (сам): вернуть себя._traverse_dict (сам .__ dict__) |
Детали реализации просты и зависят от динамического доступа к атрибутам с использованием hasattr, динамической проверки типов с помощью isinstance
и доступа к словарю экземпляра __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 = право |
Перевод большого количества связанных объектов Python в словарь становится простым:
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': нет}} |
Самое приятное в миксах — это то, что вы можете сделать их общие функциональные возможности подключаемыми, чтобы при необходимости их поведение можно было переопределить.Например, здесь я определяю подкласс BinaryTree
, который содержит ссылку на своего родителя. Эта круговая ссылка приведет к тому, что орудие по умолчанию
Операция ToDictMixin.to_dict
для бесконечного цикла:
класс BinaryTreeWithParent (BinaryTree): def __init __ (self, value, left = None, right = None, parent = None): super () .__ init __ (значение, слева = слева, справа = справа) self.parent = родитель |
Решение состоит в том, чтобы переопределить BinaryTreeWithParent._traverse
для обработки только важных значений, предотвращая циклы, встречающиеся при смешивании. Здесь переопределение _traverse
вставляет числовое значение родителя и, в противном случае, подчиняется реализации микширования по умолчанию.
с помощью встроенной супер функции:
def _traverse (self, key, value): if (isinstance (значение, BinaryTreeWithParent) и key == 'parent'): return value.value # Предотвратить циклы еще: вернуть super ()._traverse (ключ, значение) |
Вызов BinaryTreeWithParent.to_dict
работает без проблем, поскольку свойства циклических ссылок не соблюдаются:
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': Нет} |
Путем определения BinaryTreeWithParent._traverse
, я также включил любой класс с атрибутом типа BinaryTreeWithParent
для автоматической работы с ToDictMixin
:
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}} |
Mix-ins также можно составлять вместе.Например, скажем, мне нужна смесь, обеспечивающая универсальную сериализацию JSON для любого класса. Я могу сделать это, предположив, что класс предоставляет метод to_dict
(который может предоставляться или не предоставляться классом ToDictMixin
):
import json класс JsonMixin: @classmethod def from_json (cls, data): kwargs = json.loads (данные) return cls (** kwargs) def to_json (сам): вернуть json.dumps (self.to_dict ()) |
Обратите внимание, как класс JsonMixin
определяет как методы экземпляра, так и методы класса.Mix-ins позволяют добавлять к подклассам любое поведение. В этом примере единственными требованиями подкласса JsonMixin
являются предоставление метода to_dict
и получение аргументов ключевого слова для метода __init__
(см. Правило 23: «Обеспечьте дополнительное поведение с аргументами ключевого слова»).
Это сочетание упрощает создание иерархий служебных классов, которые можно сериализовать в JSON и из него с помощью небольшого шаблона. Например, здесь у меня есть иерархия классов данных, представляющих части топологии центра обработки данных:
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 = диск |
Сериализация этих классов в JSON и обратно очень проста.Здесь я проверяю, что данные могут быть отправлены в оба конца посредством сериализации и десериализации:
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 (туда и обратно) |
Когда вы используете подобные микшеры, это нормально, если класс, к которому вы применяете JsonMixin
, уже наследует от JsonMixin
выше в иерархии классов. Результирующий класс будет вести себя так же, благодаря поведению super
.
Что следует помнить
- Избегайте использования множественного наследования с атрибутами экземпляра и
__init__
, если смешанные классы могут достичь того же результата. - Используйте подключаемые варианты поведения на уровне экземпляра, чтобы обеспечить индивидуальную настройку для каждого класса, когда это может потребоваться для смешанных классов.
Что нужно помнить - Избегайте использования множественного наследования с атрибутами экземпляра и
__init__
, если смешанные классы могут достичь того же результата. - Используйте подключаемые варианты поведения на уровне экземпляра, чтобы обеспечить индивидуальную настройку для каждого класса, когда это может потребоваться для смешанных классов.
Элемент 42: Предпочитать общедоступные атрибуты частным
В Python существует только два типа видимости атрибутов класса: общедоступный и частный:
class MyObject: def __init __ (сам): себя.public_field = 5 self .__ private_field = 10 def get_private_field (сам): вернуть self .__ private_field |
Общедоступные атрибуты могут быть доступны любому, кто использует оператор точки на объекте:
foo = MyObject () утверждать foo.public_field == 5 |
Частные поля указываются с помощью префикса имени атрибута с двойным подчеркиванием. Доступ к ним можно получить напрямую с помощью методов содержащего класса:
assert foo.get_private_field () == 10
Однако прямой доступ к частным полям извне класса вызывает исключение:
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 |
Поведение частного атрибута реализуется с помощью простого преобразования имени атрибута. Когда компилятор Python видит доступ к частному атрибуту в таких методах, как MyChildObject.get_private_field
, он преобразует доступ к атрибуту __private_field
для использования вместо этого имени _MyChildObject__private_field
. В приведенном выше примере __private_field
определено только в MyParentObject .__ init__
, что означает, что настоящее имя частного атрибута — _MyParentObject__private_field
. Доступ к частному атрибуту родительского объекта из дочернего класса не удается просто потому, что преобразованное имя атрибута не существует ( _MyChildObject__private_field
вместо _MyParentObject__private_field
).
Зная эту схему, вы можете легко получить доступ к частным атрибутам любого класса — из подкласса или извне — без запроса разрешения:
assert baz._MyParentObject__private_field == 71
Если вы посмотрите в словарь атрибутов объекта, вы увидите, что частные атрибуты фактически сохраняются с именами, как они появляются после преобразования:
print (baz .__ dict__) >>> {'_MyParentObject__private_field': 71} |
Почему синтаксис частных атрибутов на самом деле не обеспечивает строгую видимость? Самый простой ответ — один из часто цитируемых девизов Python: «Мы все здесь взрослые по согласию.»Это означает, что нам не нужен язык, чтобы мешать нам делать то, что мы хотим делать. Это
наш индивидуальный выбор — расширить функциональность по своему желанию и взять на себя ответственность за последствия такого риска. Программисты Python считают, что преимущества открытости — разрешение незапланированного расширения классов по умолчанию — перевешивают недостатки.
Кроме того, возможность подключать языковые функции, такие как доступ к атрибутам (см. Правило 47: «Используйте __getattr__
, __getattribute__
и __setattr__
для ленивых атрибутов») позволяет вам возиться с внутренними компонентами объектов всякий раз, когда ты хочешь.Если вы можете это сделать, то какой
значение Python, пытающегося предотвратить доступ к частным атрибутам в противном случае?
Чтобы минимизировать ущерб от несознательного доступа к внутренним устройствам, программисты Python следуют соглашению об именах, определенному в руководстве по стилю (см. Пункт 2: «Следуйте руководству по стилю PEP 8»). Поля с префиксом одного подчеркивания (например, _protected_field
) защищены соглашением, что означает, что внешние пользователи класса должны действовать с осторожностью.
Однако многие программисты, которые плохо знакомы с Python, используют частные поля, чтобы указать внутренний API, к которому не должны обращаться подклассы или извне:
class MyStringClass: def __init __ (self, value): себя.__value = значение def get_value (сам): return str (значение self .__) foo = MyStringClass (5) assert foo.get_value () == '5' |
Это неправильный подход. Неизбежно кто-то — возможно, даже вы — захотите создать подкласс вашего класса, чтобы добавить новое поведение или устранить недостатки в существующих методах (например, способ, которым
MyStringClass.get_value
всегда возвращает строку). Выбирая частные атрибуты, вы только делаете переопределения и расширения подклассов громоздкими и хрупкими.Ваши потенциальные подклассы по-прежнему будут обращаться к закрытым полям, когда им это абсолютно необходимо:
class MyIntegerSubclass (MyStringClass): def get_value (сам): вернуть int (self._MyStringClass__value) foo = MyIntegerSubclass ('5') assert foo.get_value () == 5 |
Но если иерархия классов изменится под вами, эти классы сломаются, потому что ссылки на частные атрибуты больше не действительны. Здесь непосредственному родительскому классу MyIntegerSubclass
, MyStringClass
, был добавлен еще один родительский класс, называемый MyBaseClass
:
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) # Не обновляется |
Атрибут __value
теперь назначается в родительском классе MyBaseClass, а не в родительском классе MyStringClass
. Это вызывает ссылку на частную переменную self._MyStringClass__value
для разбиения на MyIntegerSubclass
:
foo = MyIntegerSubclass (5) foo.get_value () >>> Проследить ... AttributeError: объект MyIntegerSubclass не имеет атрибута _MyStringClass__value |
В общем, лучше допустить ошибку, позволив подклассам делать больше, используя защищенные атрибуты. Задокументируйте каждое защищенное поле и объясните, какие поля являются внутренними API-интерфейсами, доступными для подклассов, а какие следует полностью оставить в покое.Это такой же совет для других программистов, как и руководство для вас в будущем о том, как безопасно расширять собственный код:
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} должны быть разными') >>> привет и привет должны быть разными |
Это прежде всего проблема с классами, которые являются частью общедоступного API; подклассы находятся вне вашего контроля, поэтому вы не можете выполнить рефакторинг для решения проблемы.Такой конфликт особенно возможен с очень распространенными именами атрибутов (например, 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 и привет разные |
Что следует помнить
- Частные атрибуты не соблюдаются строго компилятором Python.
- С самого начала спланируйте, чтобы подклассы могли делать больше с вашими внутренними API и атрибутами, вместо того, чтобы их блокировать.
- Используйте документацию по защищенным полям для управления подклассами вместо того, чтобы пытаться принудительно управлять доступом с помощью частных атрибутов.
- Используйте только частные атрибуты, чтобы избежать конфликтов имен с подклассами, которые находятся вне вашего контроля.
Элемент 43: Наследование от collections.abc для пользовательских типов контейнеров
Большая часть программирования на Python заключается в определении классов, содержащих данные, и описании того, как такие объекты связаны друг с другом. Каждый класс Python представляет собой своего рода контейнер, объединяющий вместе атрибуты и функциональные возможности.Python также предоставляет встроенные типы контейнеров для
управление данными: списками, кортежами, наборами и словарями.
Когда вы разрабатываете классы для простых случаев использования, таких как последовательности, естественно захотеть создать подкласс встроенного типа списка Python напрямую. Например, скажем, я хочу создать свой собственный тип настраиваемого списка, у которого есть дополнительные методы для подсчета частоты его членов:
класс FrequencyList (список): def __init __ (я, члены): супер().__init __ (участники) частота def (self): counts = {} для элемента в себе: counts [item] = counts.get (item, 0) + 1 счетчики возврата |
Создавая подклассы list, я получаю все стандартные функции списка и сохраняю семантику, знакомую всем программистам Python. Я могу определить дополнительные методы для обеспечения любого необходимого мне поведения:
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 = право |
Как сделать так, чтобы этот класс действовал как тип последовательности? Python реализует поведение своего контейнера с помощью методов экземпляра, которые имеют специальные имена. Когда вы получаете доступ к элементу последовательности по индексу:
, он будет интерпретироваться как:
Чтобы класс BinaryNode
действовал как последовательность, вы можете предоставить настраиваемую реализацию __getitem__
(часто произносится как «dunder getitem» как сокращение от «getitem с двойным подчеркиванием»), который сначала проходит по глубине дерева объектов:
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] |
Проблема в том, что реализации __getitem__
недостаточно для обеспечения всей семантики последовательности, которую вы ожидаете от экземпляра списка:
len (tree) >>> Проследить ... TypeError: объект типа IndexableNode не имеет len () |
Для встроенной функции len
требуется другой специальный метод с именем __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 |
К сожалению, этого все еще недостаточно для того, чтобы класс полностью был действительной последовательностью.Также отсутствуют методы подсчета и индексации, которые программист Python ожидал бы увидеть в последовательности, такой как список или кортеж. Оказывается, определение ваших собственных типов контейнеров — это очень
сложнее, чем кажется.
Чтобы избежать этой трудности во вселенной Python, встроенный модуль collections.abc
определяет набор абстрактных базовых классов, которые предоставляют все типичные методы для каждого типа контейнера. Когда вы создаете подкласс от этих абстрактных базовых классов и забываете реализовать
требуемые методы, модуль сообщает, что что-то не так:
из коллекций.Последовательность импорта abc класс BadType (Последовательность): проходить foo = BadType () >>> Проследить ... TypeError: невозможно создать экземпляр абстрактного класса BadType с помощью абстрактных методов __getitem__, __len__ |
Когда вы реализуете все методы, требуемые абстрактным базовым классом из collections.abc
, как я сделал выше с SequenceNode
, он предоставляет все дополнительные методы, такие как index
и count
, бесплатно:
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 |
Преимущества использования этих абстрактных базовых классов еще больше для
более сложные типы контейнеров, такие как Set
и MutableMapping
, которые
иметь большое количество специальных методов, которые необходимо реализовать
чтобы соответствовать соглашениям Python.
Помимо модуля collections.abc
, Python использует множество специальных
методы сравнения и сортировки объектов, которые могут быть предоставлены
как контейнерными, так и неконтейнерными классами (см. Правило 73:
«Знайте, как использовать heapq для приоритетных очередей», например).
Что следует помнить
- Наследование непосредственно от типов контейнеров Python (например, list или dict) для простых случаев использования.
- Остерегайтесь большого количества методов, необходимых для правильной реализации настраиваемых типов контейнеров.
- Сделайте так, чтобы пользовательские типы контейнеров унаследовали от интерфейсов, определенных в
collections.abc
, чтобы ваши классы соответствовали требуемым интерфейсам и поведению.
Другие обучающие программы (спонсоры)
Этот сайт щедро поддерживается
DataCamp.DataCamp предлагает интерактивные онлайн
Учебники по Python
для науки о данных. Присоединяйтесь к более чем миллиону других учеников и получите
начал изучать Python для науки о данных сегодня!
Пожалуйста, включите JavaScript, чтобы просматривать комментарии от Disqus.
PEP 245 — синтаксис интерфейса Python
PEP: | 245 |
---|---|
Название: | Синтаксис интерфейса Python |
Автор: | Мишель Пеллетье <мишель у пользователей.sourceforge.net> |
Обсуждения — Кому: | http://www.zope.org/Wikis/Interfaces |
Статус: | Отклонено |
Тип: | Standards Track |
Создано: | 11.01.2001 |
Версия Python: | 2.2 |
Пост-история: | 21 марта 2001 г. |
Я отвергаю этот PEP. Прошло уже пять лет.Хотя в некоторых
точка Я ожидаю, что у Python будут интерфейсы, это было бы наивно
ожидать, что он будет напоминать синтаксис в этом PEP. Кроме того, PEP 246
быть отвергнутым в пользу чего-то совершенно другого; интерфейсы
не будет играть роли в адаптации или чем-то еще. GvR.
Этот PEP описывает предлагаемый синтаксис для создания интерфейса
объекты в Python.
Помимо размышлений о добавлении статической системы типов в
Python, Type-SIG также поручили разработать интерфейс
система для Python.В декабре 1998 года Джим Фултон выпустил
прототип системы интерфейсов, основанный на обсуждениях SIG.
Многие вопросы и справочная информация по этому обсуждению
и прототип можно найти в архивах SIG.
Примерно в конце 2000 года компания Digital Creations начала задумываться о
улучшенный дизайн компонентных моделей для Zope. Будущее Зопа
компонентная модель сильно зависит от интерфейсных объектов. Это привело к
дальнейшее развитие прототипа интерфейса Джима «Пугало».
Начиная с версии 2.3, Zope поставляется с пакетом интерфейса как
стандартное программное обеспечение. Пакет интерфейса Zope используется в качестве
эталонная реализация для этого PEP.
Синтаксис, предложенный в этом PEP, основан на улучшении синтаксиса.
описать в PEP 232 и описывает базовую структуру
на котором может быть основан PEP 233. Есть некоторая работа
сделано в отношении объектов интерфейса и объектов прокси, поэтому для
те необязательные части этого PEP, которые вы, возможно, захотите увидеть.
Интерфейсы важны, потому что они решают ряд проблем
которые возникают при разработке программного обеспечения:
- В Python существует множество подразумеваемых интерфейсов, обычно называемых
как «протоколы».В настоящее время определение этих протоколов
основанный на самоанализе реализации, но часто это также
терпит неудачу. Например, определение __getitem__ подразумевает как
последовательность и отображение (первое с последовательными, целыми
ключи). Разработчик не может прямо сказать
какие протоколы объект намеревается реализовать. - Python ограничен, с точки зрения разработчика,
разделение между типами и классами. Когда ожидаются типы,
потребитель использует такой код, как ‘type (foo) == type («»)’, чтобы определить,
‘foo’ — это строка.Когда ожидаются экземпляры классов,
потребитель использует isinstance (foo, MyString), чтобы определить, является ли foo
является экземпляром класса MyString. Нет единого
модель для определения возможности использования объекта в определенном,
действительный способ. - Динамическая типизация Python очень гибкая и мощная, но она
не имеет преимуществ языков со статической типизацией, которые
обеспечить проверку типов. Языки со статической типизацией предоставляют вам
гораздо больше безопасности типов, но часто слишком многословны, потому что
объекты могут быть обобщены только с помощью общих подклассов и использоваться
конкретно с кастингом (например, в Java).
Есть также ряд проблем с документацией, которые сопрягаются
попробуй решить.
- Разработчики тратят много времени на изучение исходного кода
ваша система, чтобы выяснить, как работают объекты. - Разработчики, которые плохо знакомы с вашей системой, могут неправильно понять, как ваша
объекты работают, вызывая и, возможно, распространяя ошибки использования. - Поскольку отсутствие интерфейсов означает, что использование выводится из
источник, разработчики могут в конечном итоге использовать методы и атрибуты, которые
предназначены «только для внутреннего использования». - Проверка кода может быть трудной и очень обескураживающей для новичка
программисты пытаются правильно понять код, написанный гуру. - Много времени тратится впустую, когда многие очень стараются
понимать неясность (например, недокументированное программное обеспечение). Потратить усилия
интерфейсы предварительного документирования сэкономят большую часть этого времени
конец.
Интерфейсы пытаются решить эти проблемы, предоставляя вам возможность
для уточнения договорных обязательств по вашему объекту, документации
о том, как использовать объект, и о встроенном механизме обнаружения
договор и документация.
Python имеет очень полезные функции самоанализа. Это хорошо известно
что это делает изучение концепций в интерактивном интерпретаторе
проще, потому что Python дает вам возможность смотреть на все виды
информации об объектах: тип, строки документа, экземпляр
словари, базовые классы, несвязанные методы и многое другое.
Многие из этих функций ориентированы на самоанализ с использованием
и изменение реализации программного обеспечения, и один из них («док
strings «) ориентирована на предоставление документации.Этот
предложение описывает расширение этого естественного самоанализа
фреймворк, описывающий интерфейс объекта.
По большей части синтаксис интерфейсов очень похож на
синтаксис классов, но будущие потребности или потребности, поднятые в
обсуждение, может определить новые возможности синтаксиса интерфейса.
Формальное описание синтаксиса BNF дается позже в PEP,
в целях иллюстрации, вот пример двух
различные интерфейсы, созданные с использованием предложенного синтаксиса:
интерфейс CountFishInterface: «Интерфейс подсчета рыбы» def oneFish (): «Увеличивает количество рыб на единицу» def twoFish (): "Увеличивает количество рыб на два" def getFishCount (): "Возвращает количество рыбы" интерфейс ColorFishInterface: «Интерфейс раскраски рыбы» def redFish (): "Устанавливает текущий цвет рыбы на красный" def blueFish (): "Устанавливает текущий цвет рыбы на синий" def getFishColor (): "Возвращает текущий цвет рыбы"
Этот код при оценке создаст два интерфейса, называемых
CountFishInterface и ColorFishInterface.Эти интерфейсы
определяются оператором интерфейса.
Приходит прозаическая документация по интерфейсам и их методам.
из строк документации. Информация о подписи метода поступает из
подписи заявлений def. Обратите внимание, как нет тела
для операторов def. Интерфейс не реализует
служение чему-либо; он просто описывает одного. Документация
строки в интерфейсах и методах интерфейса являются обязательными,
Заявление о прохождении не может быть предоставлено. Эквивалент интерфейса
инструкция pass — это пустая строка документа.
Вы также можете создавать интерфейсы, которые «расширяют» другие интерфейсы.
Здесь вы можете увидеть новый тип интерфейса, расширяющий возможности
CountFishInterface и ColorFishInterface:
интерфейс FishMarketInterface (CountFishInterface, ColorFishInterface): «Это документация для FishMarketInterface» def getFishMonger (): "Возвращает торговца рыбой, с которым вы можете взаимодействовать" def наймNewFishMonger (имя): «Наймите нового торговца рыбой» def buySomeFish (количество = 1): «Купи рыбу на рынке»
FishMarketInterface расширяется на CountFishInterface и
ColorfishInterface.
Следующим шагом является объединение классов и интерфейсов с помощью
создание конкретного класса Python, который утверждает, что он реализует
интерфейс. Вот пример компонента FishMarket, который может
сделать это:
класс FishError (Ошибка): проходить класс FishMarket реализует FishMarketInterface: число = 0 цвет = Нет monger_name = 'Хрустящие ракушки' def __init __ (я, число, цвет): self.number = число self.color = цвет def oneFish (сам): себя.число + = 1 def twoFish (сам): self.number + = 2 def redFish (сам): self.color = 'красный' def blueFish (сам): self.color = 'синий' def getFishCount (сам): вернуть self.number def getFishColor (сам): вернуть self.color def getFishMonger (сам): вернуть self.monger_name def наймNewFishMonger (я, имя): self.monger_name = имя def buySomeFish (self, количество = 1): если количество> self.count: Raise FishError ("Недостаточно рыбы") себя.count - = количество количество возврата
Этот новый класс FishMarket определяет конкретный класс, который
реализует FishMarketInterface. Объект, следующий за
Оператор реализации называется «утверждением интерфейса». An
утверждение интерфейса может быть либо объектом интерфейса, либо кортежем
утверждения интерфейса.
Утверждение интерфейса, представленное в таком операторе класса.
хранится в атрибуте класса __implements__ class. После
интерпретируя приведенный выше пример, у вас будет оператор класса
которые можно исследовать таким образом с помощью встроенного в него «орудия»
функция:
>>> FishMarket <класс FishMarket в 8140f50> >>> Фишмаркет.__implements__ (<Интерфейс FishMarketInterface на 81006f0>,) >>> f = FishMarket (6, 'красный') >>> реализует (f, FishMarketInterface) 1 >>>
Класс может реализовать более одного интерфейса. Например, скажем, вы
имел интерфейс под названием ItemInterface, описывающий, как
объект работал как элемент в объекте-контейнере. Если бы ты хотел
утверждают, что экземпляры FishMarket реализовали ItemInterface
интерфейс, а также FishMarketInterface, вы можете предоставить
утверждение интерфейса, содержащее кортеж объектов интерфейса для
класс FishMarket:
класс FishMarket реализует FishMarketInterface, ItemInterface: #...
Утверждения интерфейса
также можно использовать, если вы хотите утверждать, что
один класс реализует интерфейс, и все интерфейсы, которые
другой класс орудий:
класс MyFishMarket реализует FishMarketInterface, ItemInterface: # ... класс YourFishMarket реализует FooInterface, MyFishMarket .__ реализует__: # ...
Этот новый класс YourFishMarket утверждает, что он реализует
FooInterface, а также интерфейсы, реализованные
MyFishMarket класс.
Стоит немного подробнее рассказать об интерфейсе.
утверждения. Утверждение интерфейса — это либо объект интерфейса, либо
или набор утверждений интерфейса. Например:
FooInterface FooInterface, (BarInterface, BobInterface) FooInterface, (BarInterface, (BobInterface, MyClass .__ реализует__))
Все ли допустимые утверждения интерфейса. Когда два интерфейса определяют
те же атрибуты, порядок, в котором информация является предпочтительной
в утверждении сверху вниз, слева направо.
Существуют и другие варианты интерфейса, которые при необходимости
простота, объединили понятие класса и интерфейса для
обеспечить простое применение интерфейса. Объекты интерфейса имеют
отложенный метод, который возвращает отложенный класс, реализующий
это поведение:
>>> FM = FishMarketInterface.deferred () >>> класс MyFM (FM): пройти >>> f = MyFM () >>> f.getFishMonger () Traceback (самый внутренний последний): Файл "", строка 1, в? Интерфейс.Исключения. Объекту не удалось реализовать интерфейс FishMarketInterface Атрибут getFishMonger не был предоставлен. >>>
Это обеспечивает небольшое пассивное принудительное использование интерфейса.
сообщая вам, что вы забыли сделать для реализации этого интерфейса.
Синтаксис Python определен в модифицированной грамматической нотации BNF.
описано в Справочном руководстве Python. Эта секция
описывает предлагаемый синтаксис интерфейса, используя эту грамматику:
interfacedef: "interface" interfacename [extends] ":" suite расширяется: "(" [список_выражений] ")" имя интерфейса: идентификатор
Определение интерфейса — это исполняемый оператор.Это первое
оценивает расширенный список, если он есть. Каждый элемент в расширении
list должен оцениваться как интерфейсный объект.
Затем набор интерфейса выполняется в новом кадре выполнения.
(см. Справочное руководство по Python, раздел 4.1), используя новый
созданное локальное пространство имен и исходное глобальное пространство имен. Когда
пакет интерфейса завершает выполнение, его кадр выполнения
отбрасывается, но его локальное пространство имен сохраняется как элементы интерфейса.
Затем создается интерфейсный объект с использованием списка расширений для
базовые интерфейсы и сохраненные элементы интерфейса.Интерфейс
имя привязано к этому объекту интерфейса в исходном локальном
пространство имен.
Этот PEP также предлагает расширение для оператора Python class:
classdef: "class" имя класса [наследование] [реализует] ":" набор орудия: «орудия» имплист имплист: список-выражений имя класса наследование, люкс список-выражений: см. Справочное руководство по Python
Перед выполнением набора классов «наследование» и
Операторы «орудия» оцениваются, если они присутствуют.В
поведение «наследования» не изменилось, как определено в Разделе 7.6.
Справочник по языку.
«Орудия», если они есть, оцениваются после наследования.
Это должно соответствовать спецификации интерфейса, которая либо
интерфейс или набор спецификаций интерфейса. Если действительный
спецификация интерфейса присутствует, утверждение присвоено
атрибут __implements__ объекта класса в виде кортежа.
Этот PEP не предлагает никаких изменений синтаксиса функции.
определения или присвоения.
Примеры интерфейсов выше не описывают никакого поведения
для своих методов они просто описывают интерфейс, типичный
Объект FishMarket будет реализован.
Вы можете заметить сходство между интерфейсами, выходящими из
другие интерфейсы и классы, подклассы от других классов.
Это похожая концепция. Однако важно отметить, что
интерфейсы расширяют интерфейсы и классы подклассов. Ты
не может расширять класс или подкласс интерфейса. Классы и
интерфейсы отдельные.
Цель класса — поделиться реализацией того, как
объект работает. Назначение интерфейса — документировать, как
работать с объектом, а не с тем, как объект реализован. это
возможно иметь несколько разных классов с очень разными
реализации реализуют один и тот же интерфейс.
Также возможно реализовать один интерфейс со многими классами
которые смешивают функциональность интерфейса или
и наоборот, один класс может реализовывать множество
интерфейсы.Из-за этого интерфейсы и классы не должны быть
запутанные или смешанные.
Полезное расширение списка встроенных функций Python в
свет интерфейсных объектов будет реализациями (). Это встроенный
ожидает два аргумента, объект и интерфейс, и возвращает
истинное значение, если объект реализует интерфейс, ложь
иначе. Например:
>>> интерфейс FooInterface: пройти >>> класс Foo реализует FooInterface: pass >>> f = Foo () >>> реализует (f, FooInterface) 1
В настоящее время эта функция существует в справочнике
реализация как функции в пакете интерфейса, требующие
«Интерфейс импорта», чтобы использовать его.Его существование в виде встроенного
было бы чисто для удобства и не обязательно для использования
интерфейсы и аналог isinstance () для классов.
Предлагаемая модель интерфейса не вводит обратного
проблемы совместимости в Python. Однако предлагаемый синтаксис
делает.
Любой существующий код, использующий интерфейс в качестве идентификатора, будет
перерыв. Могут быть и другие виды обратной несовместимости, которые
определение интерфейса как нового ключевого слова представит. Этот
расширение синтаксиса Python не изменяет существующий синтаксис
любым обратно несовместимым способом.
Новый синтаксис Python из __future__ и новое предупреждение
фреймворк идеально подходит для решения этой обратной
несовместимость. Чтобы использовать синтаксис интерфейса сейчас, разработчик мог
используйте инструкцию:
из интерфейсов импорта __future__
Кроме того, любой код, который использует интерфейс ключевых слов в качестве
идентификатор будет выдано предупреждение от Python. После
соответствующий период времени, синтаксис интерфейса станет
стандарт, приведенный выше оператор импорта ничего не сделает, и любой
идентификаторы с именем interface вызовут исключение.Этот
срок предлагается 24 месяца.
Добавление нового ключевого слова интерфейса и расширение синтаксиса класса с помощью
орудия.
Расширение интерфейса класса для включения __implements__.
Добавить встроенный в ‘инструменты (объект, интерфейс)’.
Этот PEP предлагает добавить одно новое ключевое слово в язык Python,
интерфейс. Это нарушит код.
Этот PEP еще не обсуждался на python-dev.
Этот документ размещен в открытом доступе.
Источник: https://github.com/python/peps/blob/master/pep-0245.txt
Введение в интерфейс Python — Руководства по Python
В этом руководстве по Python мы обсудим интерфейс python , а также проверим, что такое интерфейс в Python с примерами. Давайте узнаем:
- Что такое интерфейс в Python?
- Как создать интерфейс на Python?
- Что такое формальный и неформальный интерфейс в Python?
- Примеры интерфейса Python
- Интерфейс Python против абстрактного класса
Что такое интерфейс?
- Интерфейс — это набор сигнатур методов, которые должны быть предоставлены реализующим классом.
- Интерфейс содержит абстрактные по своей природе методы. У абстрактных методов будет единственное объявление, поскольку реализации нет.
- Интерфейс в python определяется с помощью класса python и является подклассом interface.Interface, который является родительским интерфейсом для всех интерфейсов.
- Реализации будут выполнены классами, которые унаследуют интерфейс. Интерфейсы в Python немного отличаются от других языков, таких как Java, C # или C ++.
- Реализация интерфейса — это способ написания организованного кода.
Давайте разберемся с интерфейсами Python на нескольких примерах.
Как объявить интерфейс в Python
Здесь мы увидим, как объявить интерфейсный модуль в Python.
Синтаксис:
класс MyInterface (zope.interface.Interface)
- Во-первых, мы импортируем модуль zope.interface .
- zope.interface — это модуль, который используется для реализации объектного интерфейса в Python.
- Библиотека zope.interface — это выход, когда что-то неясно.
- Интерфейс действует как образец для проектирования классов. Здесь @ zope.interface.implementer (Lunch) реализовано с использованием декоратора реализации в классе.
- Этот пакет напрямую экспортирует атрибуты и интерфейсы.
- Для преодоления неопределенности интерфейса реализован модуль zope .
Реализация (класс) — эта функция возвращает логическое значение. Если класс реализует интерфейс, результатом будет True else False .
Пример:
import zope.interface
class Lunch (zope.interface.Interface):
northindian = zope.interface.Attribute ("шоколад")
def food (self, northindian):
pass
def холодные напитки (самостоятельно, напитки):
pass
@ zope.interface.implementer (Обед) класс Обед (zope.interface.Interface):
def food (self, northindian):
return northindian
def холодные напитки (self, напитки):
return beverages
colddrinks = Lunch ['colddrinks']
food = Lunch ['food']
print (Lunch.implementedBy (Обед))
print (тип (холодные напитки))
print (тип (еда))
Здесь мы видим, что класс реализован в интерфейсе. Итак, возвращается логическое значение true . Кроме того, мы можем увидеть результат, дважды превышающий
На изображении ниже показан результат:
Модуль интерфейса Python
Создание интерфейса Python
Существует два способа создания и реализации интерфейса на python : —
- Неформальный интерфейс
- Формальный интерфейс
Неформальный интерфейс в Python
Неформальный интерфейс в Python — это класс.Он определяет методы, которые можно переопределить, но без принудительного применения. Неформальный интерфейс в python называется протоколом, потому что он неформальный и не может применяться формально. Обычно для выполнения некоторых операций используются следующие методы:
- __iter__ — этот метод возвращает итератор для объекта.
- __len__ — Этот метод возвращает длину строки, списка, словаря, кортежа.
- __contain__ — этот метод используется для проверки, содержит ли он другую строку.
Пример:
класс Шоколад:
def __init __ (self, items):
self .__ items = items
def __len __ (сам):
вернуть len (self .__ items)
def __contains __ (self, items):
возвращать товары в self .__ items
fav_chocolate = Шоколад (["киткат", "молочко", "жевать", "5 звезд"])
печать (len (fav_chocolate))
print ("kitkat" в fav_chocolate)
print ("жевать" нет в fav_chocolate)
print ("dirymilk" отсутствует в fav_chocolate)
print ("5 звезд" в fav_chocolate)
- В этом примере я реализовал __len__ и __contain__.Мы можем напрямую использовать функцию len () для экземпляра шоколада, а затем нам нужно проверить элемент, присутствует ли он в списке.
- Использование в операторе , print (len (fav_chocolate)) используется для определения длины списка.
- Здесь мы видим, что он возвращает логическое значение . Если элемент присутствует в списке, он вернет true, иначе он вернет false. На скриншоте ниже показан результат:
Неформальный интерфейс в Python
Формальный интерфейс в Python (ABC)
Здесь мы видим формальный интерфейс в python .
- Формальный интерфейс — это интерфейс, который применяется формально. Для создания формального интерфейса нам нужно использовать ABC (абстрактные базовые классы).
- Азбука объясняется, поскольку мы определяем класс, который является абстрактным по своей природе, мы также определяем методы базового класса как абстрактные методы.
- Любой объект, производный от базовых классов, вынужден реализовывать эти методы.
- В этом примере у меня импортировал модуль abc и определил класс Food. @ abc.abstractmethod — это декоратор, указывающий абстрактные методы, которые используются для объявления абстрактных методов для свойств.
- Я определил функцию def вкус , используя ключевое слово def, с помощью ключевого слова self мы можем получить доступ к атрибутам и методам класса.
- И я также определил подкласс как class north_indian и затем распечатал экземпляр из класса food. Оператор передачи используется как заполнитель.
Пример:
импорт abc
класс Еда (abc.ABC):
@ abc.abstractmethod
def вкус (сам):
проходить
class north_indian (Еда):
def вкус (сам):
print («Готовим!»)
s = северный_индий ()
print (isinstance (s, Food))
На скриншоте ниже показан результат:
В этом выводе мы видим, что выводится логическое значение. Он возвращает true, только если экземпляр присутствует в классе, иначе он возвращает false.
Формальный интерфейс в Python (ABC)
Примеры интерфейса Python
Здесь мы увидим, как код для производного класса определяет абстрактный метод.Итак, у нас есть импортированный модуль abc , и у нас есть имя класса myinterface (abc.ABC) .
Пример:
импорт abc
класс myinterface (abc.ABC):
@ abc.abstractclassmethod
def display ():
проходить
print ("Это мой класс")
класс Myclass (myinterface):
def display ():
проходить
obj = Myclass ()
Здесь вызывается obj = Myclass () и выводит результат как «Это Myclass». Вы можете обратиться к приведенному ниже снимку экрана с примерами интерфейса Python.
Примеры интерфейса Python
Несколько интерфейсов Python
Теперь мы можем видеть несколько интерфейсов в Python .
В приведенном ниже примере нам нужно import abc module, а затем мы можем инициализировать класс как Food и подкласс как northIndian () и southIndian ().
Пример:
импорт abc
класс Еда (abc.ABC):
@ abc.abstractmethod
def вкус (сам):
проходить
class northIndian (Еда):
def вкус (сам):
print («Готовим!»)
класс Еда (abc.ABC):
@ abc.abstractmethod
def вкус (сам):
проходить
класс southIndian (Еда):
def вкус (сам):
print ("Готовим! ..")
а = северноиндийский ()
s = южноиндийский ()
print (isinstance (s, northIndian))
print (isinstance (s, южноиндийский))
print (isinstance (a, northIndian))
print (isinstance (a, южноиндийский))
Здесь мы можем видеть в выходных данных значение false, поскольку экземпляра s назначено southIndian , но в операторе печати ему присвоено значение (s, northIndian) .Мы можем сослаться на скриншоты ниже:
Несколько интерфейсов Python
интерфейс Python против абстрактного класса
Давайте поймем разницу между интерфейсом Python и абстрактным классом .
Интерфейс Python | Абстрактный класс Python |
Интерфейс — это набор методов и атрибутов этого объекта. | Мы можем использовать абстрактный базовый класс для определения и обеспечения интерфейса. |
Все методы интерфейса абстрактные | Абстрактный класс может иметь абстрактные методы, а также конкретные методы. |
Мы используем интерфейс, если все функции должны быть реализованы по-разному для разных объектов. | Абстрактные классы используются, когда есть некоторая общая черта, общая для всех объектов как таковых. |
Интерфейс медленный по сравнению с абстрактным классом. | Абстрактные классы быстрее. |
Вам могут понравиться следующие уроки Python:
В этом руководстве по Python мы узнали об интерфейсе Python . Кроме того, мы рассмотрели следующие темы:
- Что такое интерфейс?
- Как объявить интерфейс в Python
- Как создать интерфейс в Python
- Примеры интерфейса Python
- Несколько интерфейсов Python
- Интерфейс Python против абстрактного класса
Интерфейс Python :: Anaconda.org
| статус сборки |
интерфейс
предоставляет средства для объявления интерфейсов и для статического
утверждая, что классы реализуют эти интерфейсы. Он поддерживает Python 2.7 и
Python 3.4+.
Интерфейс
улучшает модуль Python abc
двумя способами:
Требования к интерфейсу проверяются во время создания класса, а не во время
время создания экземпляра. Это означает, что интерфейс
класс не соответствует требованиям интерфейса, даже если вы никогда не
создавать любые экземпляры этого класса.интерфейс
требует, чтобы сигнатуры метода реализации интерфейса
совместимы с подписями, заявленными в интерфейсе. Например,
следующий код, использующийabc
, не вызывает ошибки:.. кодовый блок :: python
из abc import ABCMeta, abstractmethod
класс Base (метакласс = ABCMeta):
… @abstractmethod
… def метод (self, a, b):
… проходить
…
Реализация класса (MyABC):
… метод def (self):
… return «Это не должно работать».
…
impl = Реализация ()Эквивалентный код с использованием интерфейса
подпись нашего метода реализации несовместима с
подпись нашего объявления интерфейса:.. кодовый блок :: python
из средств импорта интерфейса, Интерфейс
класс I (Интерфейс):
… def метод (self, a, b):
… проходить
…
класс C (реализует (I)):
… метод def (self):
… return «Это не должно работать»
…
TypeError:
класс C не смог реализовать интерфейс I:Следующие методы были реализованы, но с недействительными подписями:
— method (self)! = method (self, a, b)
Определение интерфейса
~~~~~~~~~~~~~~~~~~~~
Чтобы определить интерфейс, просто создайте подкласс от интерфейса .Интерфейс
и определить
заглушки методов в теле вашего класса.
.. кодовый блок :: python
из интерфейса импорта Интерфейс
класс MyInterface (Интерфейс):
def method1 (self):
проходить
def method2 (self, arg1, arg2):
проходить
Реализация интерфейса
~~~~~~~~~~~~~~~~~~~~~~~~
Чтобы объявить, что конкретный класс реализует интерфейс I
, передайте
реализует (I)
как базовый класс для вашего класса.
.. кодовый блок :: python
из интерфейса импорта реализует
класс MyClass (реализует (MyInterface)):
def method1 (self):
вернуть "метод1"
def method2 (self, arg1, arg2):
вернуть "метод2"
Установка
~~~~~~~~~~~~
.. кодовый блок :: оболочка
$ pip установить python-interface
.. | статус строительства | изображение :: https://travis-ci.org/ssanderson/interface.svg?branch=master
: target: https: // travis-ci.org / ssanderson / interface
Создание неявных интерфейсов на Python с классами протоколов
В
Python 3.8 реализована одна из самых крутых функций, которые я видел в последней версии Python: классы протоколов.
Эта функция позволяет создавать интерфейсы и проверять, что объекты неявно им удовлетворяют, так же, как это можно сделать в Go. Вы могли бы назвать это статическим набором уток .
Заинтригованы? Давайте посмотрим, как работают классы протоколов, написав код.
Пятиминутная версия
Если вы просто хотите увидеть, как это работает, ознакомьтесь со следующим кодом.Это не так много, но не выполняет статическую проверку типов с mypy
…
.
от ввода протокола импорта
класс Flyer (Протокол):
def fly (self) -> Нет:
"" "Летчик умеет летать" ""
класс FlyingHero:
"" "Этот герой умеет летать, а это ЗВЕРЬ." ""
def fly (self):
# Попробуй полетать ...
класс RunningHero:
"" "Этот герой умеет бежать. Лучше, чем ничего!" ""
def run (self):
# Беги, если твоя жизнь тебе дорога!
класс Board:
"" "Воображаемая игровая доска, которая ничего не делает."" "
def make_fly (self, obj: Flyer) -> None: # <- Вот волшебство
"" "Заставьте объект летать." ""
вернуть obj.fly ()
def main () -> Нет:
board = Доска ()
board.make_fly (FlyingHero ())
board.make_fly (RunningHero ()) # <- Ошибка проверки типов mypy!
если __name__ == '__main__':
основной()
Вы это уловили ?!
У нас есть Flyer
, FlyingHero
и Board
. Board
может заставить вещи летать - но она может только заставить вещи летать, как может летать .
Это не означает, что Board
может заставить летать только вещи подкласса Flyer
. Обратите внимание, что FlyingHero
не является подклассом Flyer
.
Классы протоколов позволяют нам определять интерфейс, называемый протоколом, и использовать статическую проверку типов с помощью mypy, чтобы убедиться, что объекты удовлетворяют интерфейсу - без необходимости классам объявлять, что они удовлетворяют интерфейсу или подклассу чего-либо.
Итак, почему это называется «протоколом» и для чего он нужен?
Давайте глубже ответим на эти вопросы.
Протоколы
Встроенная функция len ()
работает с любым объектом, имеющим метод __len __ ()
. Объекты не должны объявлять, что у них есть метод __len __ ()
или подкласс каких-либо специальных классов для работы с len ()
.
Программисты
Python называют это положение дел протоколом, и наиболее распространенным примером, вероятно, является протокол итераций. Так является ли «протокол» в этом контексте просто причудливым словом для «интерфейса»?
Не совсем так.
Протокол обычно включает в себя набор методов, которые начинаются и заканчиваются двойным подчеркиванием, определяющим поведение, например итерацию.
Но что именно находится в этом наборе методов? До Python 3.5 вам приходилось искать документацию, чтобы ответить на этот вопрос.
Python 3.5 формально определил методы в каждом протоколе, но сделал это с использованием абстрактных базовых классов в модуле collections.abc.
Если вы хотите подключиться к протоколу, вы можете использовать ABC в качестве документации, но вы не можете использовать mypy
для проверки того, что объект реализует протокол, если объект не является подклассом одной из этих ABC.
Это противоречит природе протоколов, потому что они всегда полагались на утиную типизацию или способность программы Python проверять поведение объекта - с помощью его атрибутов и методов - а не его типа.
Куда я иду со всем этим?
Ну, Python 3.8 исправил проблему, создав механизм, который mypy
мог бы использовать для проверки, реализует ли объект протокол: классы протокола.
Классы протокола
В то время как в Java интерфейсы являются явными, протоколы Python всегда были… ну, невидимыми!
Python 3.Классы протоколов 8 делают протоколы более явными. По сути, протоколы становятся больше похожими на интерфейсы в Go.
Сначала вы определяете класс протокола:
Флаер класса
(протокол):
def fly (self) -> Нет:
"" "Летчик умеет летать" ""
Затем вы можете использовать класс протокола в качестве аннотации типа, и mypy
проверит, реализует ли объект протокол.
класс Доска:
"" "Воображаемая игровая доска, которая ничего не делает."" "
def make_fly (self, obj: Flyer) -> None: # <- Вот волшебство
"" "Заставьте объект летать." ""
вернуть obj.fly ()
Код obj: Flyer
является важной частью - это аннотация типа, которую mypy
теперь может использовать для проверки, реализует ли obj
протокол Flyer
.
Итак, для чего это можно использовать?
Каждый раз, когда вы будете использовать интерфейс на другом языке, теперь вы можете делать то же самое с классами протоколов в Python.
Если раньше вы могли определить ABC и разделить его на подклассы как способ определения (и проверки типа, с mypy
) интерфейса, теперь вы можете использовать класс протокола.
Это особенно полезно, если вы пишете библиотеку и хотите упростить пользователям подключение к вашей системе с помощью настраиваемых типов.
Чтобы узнать больше о причинах этой функции, ознакомьтесь с предложением PEP.
Более длинный пример
Я извлек пятиминутный пример, с которого начался этот пост, из более длинного, но все же однофайлового примера.
Я представляю его вам здесь, как и в списках программ, найденных в бумажных журналах прошлого.
"" "
protocol_classes.py: Пример использования классов протокола.
"" "
из классов данных импортировать класс данных
из enum import Enum
от ввода импорта Любые
от ввода протокола импорта
направление класса (Enum):
N = 'n'
E = 'e'
S = 's'
W = 'ш'
NE = 'ne'
SE = 'se'
SW = 'SW'
NW = 'nw'
@dataclass
класс Движение:
"" "Движение объекта в определенном направлении."" "
obj: Любой
distance_meters: int
direction: Направление
класс Официант (протокол):
def wait (self, actions_spent: int, direction: Direction) -> Движение:
"" "Официант может подождать" ""
класс Flyer (Протокол):
def fly (self, actions_spent: int, direction: Direction) -> Движение:
"" "Летчик умеет летать" ""
класс Runner (протокол):
def run (self, actions_spent: int, direction: Direction) -> Движение:
"" "Бегун может бегать" ""
класс BaseHero:
@имущество
def max_speed (self) -> int:
возврат 1
def wait (self, actions_spent: int, direction: Direction) -> Движение:
обратное движение (obj = self, distance_meters = 0, direction = direction)
класс RunningHero (BaseHero):
"" "Этот герой умеет бегать, что лучше, чем ничего."" "
@имущество
def run_speed (self) -> int:
вернуть self.max_speed * 2
def run (self, actions_spent: int, direction: Direction) -> Движение:
возврат Движение (obj = self,
distance_meters = self.run_speed * actions_spent,
direction = направление)
класс FlyingHero (BaseHero):
"" "Этот герой умеет летать, а это ЗВЕРЬ." ""
@имущество
def fly_speed (self) -> int:
вернуть self.max_speed * 5
def fly (self, actions_spent: int, direction: Direction) -> Движение:
возврат Движение (obj = self,
distance_meters = self.fly_speed * actions_spent,
direction = направление)
класс Board:
"" "Воображаемая игровая доска, которая ничего не делает." ""
def make_wait (self, obj: Waiter, direction: Direction,
actions_spent: int) -> Движение:
"" "Заставить объект ждать.
obj - это объект, заставляющий ждать
`` действия '' - это количество последовательных действий, предпринятых для ожидания
Возвращает общее расстояние в метрах, на которое `` фигура '' прошла по доске.
пока жду (могло случиться...).
"" "
return obj.wait (actions_spent, direction)
def make_run (self, obj: Runner, direction: Направление,
actions_spent: int) -> Движение:
"" "Запустить объект.
obj - это объект, который нужно запустить
`` действия '' - это количество последовательных действий, предпринятых для перемещения
Возвращает общее расстояние в метрах, на которое объект obj прошел по доске.
"" "
return obj.run (actions_spent, direction)
def make_fly (self, obj: Flyer, direction: Направление,
actions_spent: int) -> Движение:
"" "Заставьте объект летать.obj - это объект, который нужно заставить летать
`` действия '' - это количество последовательных действий, предпринятых для перемещения
Возвращает общее расстояние в метрах, на которое объект obj прошел по доске.
"" "
return obj.fly (actions_spent, direction)
def main () -> Нет:
board = Доска ()
waiter_move = board.make_wait (BaseHero (), Direction.N, 2)
runner_move = board.make_run (RunningHero (), Direction.NW, 2)
flyer_move = board.make_fly (FlyingHero (), Направление.S, 2)
heroes = (('Герой', 'Всего перемещено клеток (M)'),
('ожидающий герой', waiter_move.distance_meters),
('бегущий герой', runner_move.distance_meters),
('летающий герой', flyer_move.distance_meters))
печать ('\ п')
для описания, движение в героях:
print ("\ t {: <22} {:> 25}". формат (описание, движение))
если __name__ == '__main__':
основной()
Фото Алекса Смита.
.