Создать программу на python windows

#статьи


  • 0

Знакомимся с библиотекой Tkinter — пишем на Python кросс-платформенный калькулятор, который рассчитывает вес человека.

Иллюстрация: Merry Mary для Skillbox Media

Антон Яценко

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

Десктопные приложения пишут на разных языках программирования: C++, C#, C, Python и других. Начинающим разработчикам проще всего использовать Python и его библиотеки для работы над графическими интерфейсами.

Одна из таких библиотек — Tkinter. Она входит в стандартный пакет Python и позволяет создавать приложения для Windows, mac OS и Linux. Давайте разберёмся, как устроена эта библиотека, и напишем десктопный калькулятор, помогающий рассчитать вес человека.

GUI (Graphical User Interface) — это графический интерфейс пользователя, оболочка программы, с которой мы взаимодействуем с помощью клавиатуры и мыши. На современных операционных системах почти все программы работают с графическим интерфейсом, и мы каждый день сталкиваемся с GUI: читаем статьи в браузере, набираем текст в редакторе или играем в игры.

Противоположность графическому интерфейсу — командная строка, позволяющая управлять приложением с помощью текстовых команд. Такой интерфейс реализован в терминале macOS и командной строке Windows.

Для работы с GUI в Python есть четыре библиотеки:

  • Tkinter;
  • Kivy;
  • Python QT;
  • wxPython.

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

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

Схематично работу с Tkinter можно представить в виде четырёх шагов:

Скриншот: Tkinter

Что здесь происходит:

  • Мы подключаем библиотеку Tkinter с помощью директивы import.
  • Создаём главное окно приложения, в котором будут размещаться все графические элементы.
  • Добавляем виджеты — визуальные элементы, выполняющие определённые действия.
  • Создаём главный цикл событий — он включает в себя все события, происходящие при взаимодействии пользователя с интерфейсом.

Ключевые объекты в работе с Tkinter — виджеты. Это аналоги тегов из HTML, которые позволяют создавать интерактивные и неинтерактивные элементы, например надписи или кнопки. Всего их 18, но чаще всего используют следующие:

  • Button — кнопки;
  • Canvas — «холст», на котором рисуют графические фигуры;
  • Entry — виджет для создания полей ввода;
  • Label — контейнер для размещения текста или изображения;
  • Menu — виджет для создания пунктов меню.

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

Мы напишем калькулятор индекса массы тела. ИМТ — это важный медицинский показатель, который позволяет оценить, есть ли у человека избыточный вес или ожирение. Он рассчитывается по следующей формуле: ​​

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

Изображение: Skillbox Media

Писать код на Python лучше всего в специальной IDE, например в PyCharm или Visual Studio Code. Они подсвечивают синтаксис и предлагают продолжение кода — это сильно упрощает работу программиста. Весь код из этой статьи мы писали в Visual Studio Code.

Библиотека Tkinter предустановлена в Python. Поэтому её нужно только импортировать:

import tkinter as tk

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


Прежде чем писать код, необходимо ответить на несколько вопросов:

  • Какие данные мы хотим получить от пользователя и в каком виде?
  • Какое событие будет запускать расчёт ИМТ: нажатие кнопки, получение приложением всех необходимых данных или что-то другое?
  • Как будем показывать результат?

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

Схематично графический интерфейс нашего калькулятора будет выглядеть так:

Скриншот: Tkinter / Skillbox Media

Теперь попробуем реализовать интерфейс и работу калькулятора с помощью Python и Tkinter.


После импорта библиотеки в Python загрузим её методы:

from tkinter import *
from tkinter import messagebox

Первая строка позволяет нам загрузить все методы Tkinter и использовать их в коде без ссылки на их наименование. Второй строкой мы явно импортируем метод messagebox, который будем использовать для вывода всплывающего окна с результатом. Это удобно, так как метод потребуется нам несколько раз.

Теперь создадим окно нашего приложения. Для этого воспользуемся модулем Tk. Приложение назовём «Калькулятор индекса массы тела (ИМТ)»:

window = Tk() #Создаём окно приложения.
window.title("Калькулятор индекса массы тела (ИМТ)") #Добавляем название приложения.

После запуска кода ничего не произойдёт. Это не ошибка. На самом деле код выполнился и окно закрылось. Необходимо явно указать, что окно приложения не должно закрываться до тех пор, пока пользователь сам не сделает этого. Для этого к коду добавим функцию window.mainloop (), указывающую на запуск цикла событий:

window.mainloop()

Запустив код, увидим экран приложения:

Скриншот: Tkinter / Skillbox Media

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

window.geometry('400x300')

Теперь название приложения видно полностью:

Скриншот: Tkinter / Skillbox Media

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

  • pack — используется, когда мы работаем с контейнерами для элементов. Позволяет позиционировать кнопки, надписи или другие элементы внутри контейнеров.
  • place — позволяет позиционировать элементы, указывая точные координаты.
  • grid — размещает элементы по ячейкам условной сетки, разделяющей окно приложения.

Мы воспользуемся комбинацией методов pack и grid. Для начала создадим виджет Frame для размещения надписей, полей ввода и кнопок. Подробное описание работы виджета есть в документации. Мы же используем только два свойства: padx и pady.

Обозначим отступы по вертикали и горизонтали в 10 пикселей для элементов, которые будут расположены внутри Frame:

frame = Frame(
   window, #Обязательный параметр, который указывает окно для размещения Frame.
   padx = 10, #Задаём отступ по горизонтали.
   pady = 10 #Задаём отступ по вертикали.
)
frame.pack(expand=True) #Не забываем позиционировать виджет в окне. Здесь используется метод pack. С помощью свойства expand=True указываем, что Frame заполняет весь контейнер, созданный для него.


В окне приложения нам необходимо добавить три вида виджетов: поле для ввода информации (Entry), текстовые надписи (Label) и кнопку (Button).

Начнём с надписей. Воспользуемся виджетом Label:

height_lb = Label(
   frame,
   text="Введите свой рост (в см)  "
)
height_lb.grid(row=3, column=1)

Мы передаём виджету Label два параметра:

  • frame — используем заготовку виджета Frame, в которой уже настроены отступы по вертикали и горизонтали.
  • text — текст, который должен быть выведен на экран.

Для позиционирования виджета используем метод grid. Укажем, что текст должен располагаться в ячейке с координатами «3-я строка, 1-й столбец». Если запустим код, то увидим там единственный элемент:

Скриншот: Tkinter / Skillbox Media

Сейчас элемент расположен в центре окна, но он займёт правильное положение, когда мы напишем другие элементы.

Добавим вторую надпись о весе аналогичным образом, но при позиционировании в grid укажем следующую, четвёртую строку:

weight_lb = Label(
   frame,
   text="Введите свой вес (в кг)  ",
)
weight_lb.grid(row=4, column=1)

Запускаем код и смотрим на результат:

Скриншот: Tkinter / Skillbox Media

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

height_tf = Entry(
   frame, #Используем нашу заготовку с настроенными отступами.
)
height_tf.grid(row=3, column=2)

Для позиционирования мы также воспользовались методом grid. Обратите внимание, что наш элемент должен быть расположен напротив надписи «Введите свой рост (в см)». Поэтому мы используем ячейку в той же строке, но уже во втором столбце. Запустим код и посмотрим на результат:

Скриншот: Tkinter / Skillbox Media

Всё получилось. Остаётся по аналогии добавить поле ввода веса:

weight_tf = Entry(
   frame,
)
weight_tf.grid(row=4, column=2, pady=5)

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

Скриншот: Tkinter / Skillbox Media

Теперь добавим кнопку, которая будет запускать расчёт ИМТ. Сделаем это с помощью виджета Button:

cal_btn = Button(
   frame, #Заготовка с настроенными отступами.
   text='Рассчитать ИМТ', #Надпись на кнопке.
)
cal_btn.grid(row=5, column=2) #Размещаем кнопку в ячейке, расположенной ниже, чем наши надписи, но во втором столбце, то есть под ячейками для ввода информации.

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

Скриншот: Tkinter / Skillbox Media

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


Напишем простую функцию и разберём её построчно:

def calculate_bmi(): #Объявляем функцию.
   kg = int(weight_tf.get()) #С помощью метода .get получаем из поля ввода с именем weight_tf значение веса, которое ввёл пользователь и конвертируем в целое число с помощью int().
   m = int(height_tf.get())/100 #С помощью метода .get получаем из поля ввода с именем height_tf значение роста и конвертируем в целое число с помощью int(). Обязательно делим его на 100, так как пользователь вводит рост в сантиметрах, а в формуле для расчёта ИМТ используются метры.
   bmi = kg/(m*m)#Рассчитываем значение индекса массы тела.
   bmi = round(bmi, 1) #Округляем результат до одного знака после запятой.

Функция готова. Но теперь нам необходимо оценить полученный результат расчёта и вывести сообщение для пользователя.


Дополним нашу функцию calculate_bmi. Воспользуемся условным оператором if, чтобы учесть полученные значения ИМТ, и методом Tkinter messagebox для отображения сообщения во всплывающем окне:

if bmi < 18.5:
       messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует недостаточному весу')
   elif (bmi > 18.5) and (bmi < 24.9):
       messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует нормальному весу')
   elif (bmi > 24.9) and (bmi < 29.9):
       messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует избыточному весу')
   else:
       messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует ожирению')

Остаётся последний шаг — наша функция должна запускаться при нажатии на кнопку «Рассчитать ИМТ». Для этого добавим свойство command в виджет Button:

cal_btn = Button(
   frame,
   text='Рассчитать ИМТ',
   command=calculate_bmi #Позволяет запустить событие с функцией при нажатии на кнопку.
)
cal_btn.grid(row=5, column=2)

Запустим код и посмотрим на результат:

Источник: Tkinter / Skillbox Media

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

from tkinter import *
from tkinter import messagebox
 
def calculate_bmi():
   kg = int(weight_tf.get())
   m = int(height_tf.get())/100
   bmi = kg/(m*m)
   bmi = round(bmi, 1)
 
   if bmi < 18.5:
       messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует недостаточному весу')
   elif (bmi > 18.5) and (bmi < 24.9):
       messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует нормальному весу')
   elif (bmi > 24.9) and (bmi < 29.9):
       messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует избыточному весу')
   else:
       messagebox.showinfo('bmi-pythonguides', f'ИМТ = {bmi} соответствует ожирению')  
 
window = Tk()
window.title('Калькулятор индекса массы тела (ИМТ)')
window.geometry('400x300')
 
 
frame = Frame(
   window,
   padx=10,
   pady=10
)
frame.pack(expand=True)
 
 
height_lb = Label(
   frame,
   text="Введите свой рост (в см)  "
)
height_lb.grid(row=3, column=1)
 
weight_lb = Label(
   frame,
   text="Введите свой вес (в кг)  ",
)
weight_lb.grid(row=4, column=1)
 
height_tf = Entry(
   frame,
)
height_tf.grid(row=3, column=2, pady=5)
 
weight_tf = Entry(
   frame,
)
weight_tf.grid(row=4, column=2, pady=5)
 
cal_btn = Button(
   frame,
   text='Рассчитать ИМТ',
   command=calculate_bmi
)
cal_btn.grid(row=5, column=2)
 
window.mainloop()

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

  • Python GUI Programming with Tkinter. Develop responsive and powerful GUI applications with Tkinter, Алан Мур.
  • Tkinter GUI Programming by Example, Дэвид Лав.

Жизнь можно сделать лучше!
Освойте востребованную профессию, зарабатывайте больше и получайте от работы удовольствие. А мы поможем с трудоустройством и важными для работодателей навыками.

Посмотреть курсы

К старту курса по разработке на Python делимся детальным руководством по работе с современным PyQt. Чтобы читать было удобнее, мы объединили несколько статей в одну:

  1. Первое приложение

  2. Слоты и сигналы

  3. Виджеты

За подробностями приглашаем под кат.


Простое приложение Hello World! на Python и Qt6

PyQt — это библиотека Python для создания приложений с графическим интерфейсом с помощью инструментария Qt. Созданная в Riverbank Computing, PyQt является свободным ПО (по лицензии GPL) и разрабатывается с 1999 года. Последняя версия PyQt6 — на основе Qt 6 — выпущена в 2021 году, и библиотека продолжает обновляться. Это руководство можно также использовать для PySide2, PySide6 и PyQt5.

Сегодня используются две основные версии: PyQt5 на основе Qt5 и PyQt6 на основе Qt6. Обе почти полностью совместимы, за исключением импорта и отсутствия поддержки некоторых продвинутых модулей из Qt6. В PyQt6 вносятся изменения в работу пространств имён и флагов, но ими легко управлять. В этом руководстве мы узнаем, как использовать PyQt6 для создания настольных приложений.

Сначала создадим несколько простых окон на рабочем столе, чтобы убедиться, что PyQt работает, и разберём базовые понятия. Затем кратко изучим цикл событий и то, как он связан с программированием графического интерфейса на Python. В заключение поговорим о QMainWindow с полезными элементами интерфейса, такими как панели инструментов и меню. Подробно я расскажу о них в следующих руководствах.

Создание приложения

Установка PyQt:

pip install pyqt6
# и на будущее
pip install pyqt-tools

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

from PyQt6.QtWidgets import QApplication, QWidget

import sys # Только для доступа к аргументам командной строки

# Приложению нужен один (и только один) экземпляр QApplication.
# Передаём sys.argv, чтобы разрешить аргументы командной строки для приложения.
# Если не будете использовать аргументы командной строки, QApplication([]) тоже работает
app = QApplication(sys.argv)

# Создаём виджет Qt — окно.
window = QWidget()
window.show()  # Важно: окно по умолчанию скрыто.

# Запускаем цикл событий.
app.exec()


# Приложение не доберётся сюда, пока вы не выйдете и цикл
# событий не остановится.

Запускаем приложение из командной строки, как и любой скрипт Python:

python3 app.py

Выполнив его, мы увидим окно. В Qt автоматически создаётся окно с обычным оформлением, возможностью его перетаскивать и менять размер. То, что вы увидите, зависит от платформы, где этот пример выполняется. Вот как отображается это окно на Windows, macOS и Linux (Ubuntu):

Окно на Windows, macOS и Linux

Окно на Windows, macOS и Linux

Разбор кода

Пройдём код построчно, чтобы понять, что именно происходит. Сначала мы импортируем классы PyQt для приложения: здесь это обработчик приложения QApplication и базовый пустой виджет графического интерфейса QWidget (оба из модуля QtWidgets):

from PyQt6.QtWidgets import QApplication, QWidget

Основные модули для Qt: QtWidgets, QtGui и QtCore.

Возможен ещё from import *, но этот вид импорта обычно не приветствуется в Python. Дальше создаём экземпляр QApplication и передаём sys.arg (список Python с аргументами командной строки, передаваемыми приложению):

app = QApplication(sys.argv)

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

app = QApplication([])

Затем создаём экземпляр QWidget, используя имя переменной window:

window = QWidget()
window.show()

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

Виджеты без родительского элемента по умолчанию невидимы. Поэтому после создания объекта window необходимо всегда вызывать функцию .show(), чтобы сделать его видимым. .show() можно удалить, но тогда, запустив приложение, вы не сможете выйти из него!

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

Наконец, вызываем app.exec(), чтобы запустить цикл события.

Что такое «цикл событий»?

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

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

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

Класс QApplication содержит цикл событий Qt (нужен один экземпляр QApplication). Приложение ждёт в цикле событий новое событие, которое будет сгенерировано при выполнении действия. Всегда выполняется только один цикл событий.

QMainWindow

Итак, в Qt любые виджеты могут быть окнами. Например, если заменить QtWidget на QPushButton. В этом примере получается окно с одной нажимаемой кнопкой:

import sys
from PyQt6.QtWidgets import QApplication, QPushButton

app = QApplication(sys.argv)

window = QPushButton("Push Me")
window.show()

app.exec()

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

В Qt уже есть решение для окна — виджет QMainWindow, имеющий стандартные функции окна для использования в приложениях, который содержит панели инструментов, меню, строку состояния, закрепляемые виджеты и многое другое. Рассмотрим эти расширенные функции позже, а пока добавим в приложение простой, пустой QMainWindow:

import sys
from PyQt6.QtWidgets import QApplication, QMainWindow

app = QApplication(sys.argv)

window = QMainWindow()
window.show()

# Запускаем цикл событий.
app.exec()

Запускаем и видим главное окно. Точно такое же, как и раньше.

QMainWindow пока не очень интересный. Добавим контент. Чтобы сделать настраиваемое окно, лучше создать подкласс QMainWindow, а затем настроить окно в блоке __init__. Так окно станет независимым в плане поведения. Итак, добавляем подкласс QMainWindow — MainWindow:

import sys

from PyQt6.QtCore import QSize, Qt
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton


# Подкласс QMainWindow для настройки главного окна приложения
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")
        button = QPushButton("Press Me!")

        # Устанавливаем центральный виджет Window.
        self.setCentralWidget(button)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

Для этого демо используем QPushButton. Основные виджеты Qt всегда импортируются из пространства имён QtWidgets, как и классы QMainWindow и QApplication. При использовании QMainWindow задействуем .setCentralWidget для размещения виджета (здесь виджет — QPushButton) в QMainWindow, по умолчанию он занимает всё окно. Как добавлять в окна несколько виджетов? Об этом поговорим рассмотрим в руководстве по макетам.

При создании подкласса из класса Qt, чтобы разрешить Qt настраивать объект, всегда нужно вызывать функцию super __init__.

В блоке __init__ сначала используем .setWindowTitle(), чтобы поменять заголовок главного окна. Затем добавляем первый виджет — QPushButton — в середину окна. Это один из основных виджетов Qt. При создании кнопки можно ввести текст, который будет на ней отображаться. Вызываем .setCentralWidget() в окне. Это специальная функция QMainWindow, которая позволяет установить виджет на середину окна.

Запускаем и снова видим окно, но на этот раз с виджетом QPushButton в центре. Нажатие кнопки ничего не даст — с этим мы разберёмся после:

QMainWindow с одной кнопкой QPushButton на Windows, macOS и Linux

QMainWindow с одной кнопкой QPushButton на Windows, macOS и Linux

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

Изменение размеров окон и виджетов

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

В Qt размеры определяются с помощью объекта QSize. Он принимает параметры ширины и высоты. Например, так создаётся окно фиксированного размера 400 x 300 пикселей:

import sys

from PyQt6.QtCore import QSize, Qt
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton


# Подкласс QMainWindow для настройки главного окна приложения
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        button = QPushButton("Press Me!")

        self.setFixedSize(QSize(400, 300))

        # Устанавливаем центральный виджет Window.
        self.setCentralWidget(button)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

Запускаем и видим окно фиксированного размера. Поменять его размер не получится.

Окно фиксированного размера

Окно фиксированного размера

Элемент управления maximize отключён на Windows и Linux. На macOS можно развернуть приложение на весь экран, но размер центрального виджета не изменится.

Кроме .setFixedSize() можно также вызвать .setMinimumSize() и .setMaximumSize(), чтобы установить минимальный и максимальный размеры соответственно. Попробуйте сами! Эти методы регулирования размеров работают в любом виджете. Продолжить изучение Python вы сможете на наших курсах:

  • Курс Python-разработчик

  • Профессия Fullstack-разработчик на Python

  • Курс «Python для веб-разработки»

А ещё вы можете приобрести книгу автора этих уроков или продолжить чтение.


Слоты и сигналы

Ранее мы рассмотрели классы QApplication и QMainWindow, цикл событий и добавили в окно простой виджет. А теперь изучим механизмы Qt для взаимодействия виджетов и окон друг с другом. В статью внесены изменения, связанные с PyQt6.

Мы создали окно и добавили в него простой виджет push button, но кнопка пока бесполезна. Нужно связать действие нажатия кнопки с происходящим. В Qt это делается с помощью сигналов и слотов или событий.

Сигналы — это уведомления, отправляемые виджетами, когда что-то происходит. Этим «чем-то» может быть что угодно — нажатие кнопки, изменение текста в поле ввода или изменение текста в окне. Многие сигналы инициируются в ответ на действия пользователя, но не только: в сигналах могут отправляться данные с дополнительным контекстом произошедшего.

Можно также писать собственные сигналы, их мы рассмотрим позже.

Слоты в Qt — это приёмники сигналов. Слотом в приложении на Python можно сделать любую функцию (или метод), просто подключив к нему сигнал. Принимающая функция получает данные, отправляемые ей в сигнале. У многих виджетов Qt есть встроенные слоты, а значит, виджеты можно подключать друг к другу напрямую.

Рассмотрим основные сигналы Qt и их использование для подключения виджетов в приложениях. Сохраните эту заготовку приложения в файле app.py:

import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton

class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

Сигналы QPushButton

Сейчас у нас есть QMainWindow с центральным виджетом QPushButton. Подключим эту кнопку к пользовательскому методу Python. Создадим простой настраиваемый слот the_button_was_clicked, принимающий сигнал clicked от QPushButton:

import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        button = QPushButton("Press Me!")
        button.setCheckable(True)
        button.clicked.connect(self.the_button_was_clicked)

        # Устанавливаем центральный виджет Window.
        self.setCentralWidget(button)

    def the_button_was_clicked(self):
        print("Clicked!")


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

Запускаем. Если нажать на кнопку, в консоли появится текст Clicked! («Нажата!»):

Clicked!
Clicked!
Clicked!
Clicked!

Получение данных

В сигналах может отправляться дополнительная информация о произошедшем. И сигнал .clicked — не исключение: с его помощью сообщается о нажатом (или переключенном) состоянии кнопки. Для обычных кнопок это значение всегда False, поэтому первый слот проигнорировал эти данные. Включим возможность нажатия кнопки, чтобы увидеть этот эффект. Ниже добавляется второй слот и выводится состояние нажатия:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        button = QPushButton("Press Me!")
        button.setCheckable(True)
        button.clicked.connect(self.the_button_was_clicked)
        button.clicked.connect(self.the_button_was_toggled)

        self.setCentralWidget(button)

    def the_button_was_clicked(self):
        print("Clicked!")

    def the_button_was_toggled(self, checked):
        print("Checked?", checked)

Запускаем! Если нажать на кнопку, она подсветится и станет checked («Нажатой»). Чтобы отключить её, нажимаем ещё раз. Найдите состояние нажатия в консоли:

Clicked!
Checked? True
Clicked!
Checked? False
Clicked!
Checked? True
Clicked!
Checked? False
Clicked!
Checked? True

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

Хранение данных

Текущее состояние виджета на Python часто хранят в переменной, что позволяет работать со значениями без доступа к исходному виджету. Причём для их хранения используются отдельные переменные или словарь. В следующем примере сохраняем значение кнопки checked («Нажата») в переменной button_is_checked в self:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.button_is_checked = True

        self.setWindowTitle("My App")

        button = QPushButton("Press Me!")
        button.setCheckable(True)
        button.clicked.connect(self.the_button_was_toggled)
        button.setChecked(self.button_is_checked)

        self.setCentralWidget(button)

    def the_button_was_toggled(self, checked):
        self.button_is_checked = checked

        print(self.button_is_checked)

Сначала устанавливаем переменной значение по умолчанию True, а затем используем это значение, чтобы установить исходное состояние виджета. Когда состояние виджета меняется, получаем сигнал и соответственно обновляем переменную.

Эта же схема применима к любым виджетам PyQt. Если в виджете нет сигнала, которым отправляется текущее состояние, нужно получить значение из виджета прямо в обработчике. Например, здесь мы проверяем состояние checked («Нажата») в нажатом обработчике:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.button_is_checked = True

        self.setWindowTitle("My App")

        self.button = QPushButton("Press Me!")
        self.button.setCheckable(True)
        self.button.released.connect(self.the_button_was_released)
        self.button.setChecked(self.button_is_checked)

        self.setCentralWidget(self.button)

    def the_button_was_released(self):
        self.button_is_checked = self.button.isChecked()

        print(self.button_is_checked)

Сохраним ссылку на кнопку в self, чтобы получить к ней доступ в слоте.

Сигнал released срабатывает, когда кнопка отпускается, при этом состояние нажатия не отправляется. Его получают из кнопки в обработчике, используя .isChecked().

Изменение интерфейса

Мы уже видели, как принимаются сигналы и выводятся на консоль результаты. Но что происходит с интерфейсом, когда нажимают на кнопку? Обновим метод слота, чтобы изменить кнопку, поменяв текст, отключив её и сделав её недоступной. И отключим пока состояние, допускающее нажатие:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        self.button = QPushButton("Press Me!")
        self.button.clicked.connect(self.the_button_was_clicked)

        self.setCentralWidget(self.button)

    def the_button_was_clicked(self):
        self.button.setText("You already clicked me.")
        self.button.setEnabled(False)

        # Также меняем заголовок окна.
        self.setWindowTitle("My Oneshot App")

Снова нужен доступ к кнопке в методе the_button_was_clicked, поэтому сохраняем ссылку на неё в self. Чтобы поменять текст кнопки, передаём str в .setText(). Чтобы отключить кнопку, вызываем .setEnabled() с аргументом False. И запускаем программу. Если нажать на кнопку, текст изменится и кнопка станет недоступной.

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

self.setWindowTitle("A new window title")

Большинство виджетов, в том числе QMainWindow, имеют свои сигналы. В следующем, более сложном примере подключим сигнал .windowTitleChanged в QMainWindow к пользовательскому методу слота. А также сделаем для этого слота новый заголовок окна:

from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton

import sys
from random import choice

window_titles = [
    'My App',
    'My App',
    'Still My App',
    'Still My App',
    'What on earth',
    'What on earth',
    'This is surprising',
    'This is surprising',
    'Something went wrong'
]


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.n_times_clicked = 0

        self.setWindowTitle("My App")

        self.button = QPushButton("Press Me!")
        self.button.clicked.connect(self.the_button_was_clicked)

        self.windowTitleChanged.connect(self.the_window_title_changed)

        # Устанавливаем центральный виджет Window.
        self.setCentralWidget(self.button)

    def the_button_was_clicked(self):
        print("Clicked.")
        new_window_title = choice(window_titles)
        print("Setting title:  %s" % new_window_title)
        self.setWindowTitle(new_window_title)

    def the_window_title_changed(self, window_title):
        print("Window title changed: %s" % window_title)

        if window_title == 'Something went wrong':
            self.button.setDisabled(True)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

Сначала создаём список заголовков окна и выбираем один из них наугад, используя встроенную функцию Python random.choice(). Подключаем пользовательский метод слота the_window_title_changed к сигналу окна .windowTitleChanged.

При нажатии на кнопку заголовок окна случайным образом изменится. Если новый заголовок окна изменится на Something went wrong («Что-то пошло не так»), кнопка отключится.

Запускаем! Нажимайте на кнопку, пока заголовок не изменится на Something went wrong. В этом примере стоит обратить внимание вот на что:

  1. Сигнал windowTitleChanged при установке заголовка окна выдаётся не всегда. Он срабатывает, только если новый заголовок отличается от предыдущего: если один и тот же заголовок устанавливается несколько раз, сигнал срабатывает только в первый раз. Чтобы избежать неожиданностей, важно перепроверять условия срабатывания сигналов при их использовании в приложении.

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

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

Подключение виджетов друг к другу напрямую

Мы уже видели примеры подключения сигналов виджетов к методам Python. Когда сигнал из виджета срабатывает, вызывается метод Python, из сигнала он получает данные. Но для обработки сигналов не всегда нужна функция Python — можно подключать виджеты друг к другу напрямую.

Добавим в окно виджеты QLineEdit и QLabel. В __init__ для окна и подключим сигнал редактирования строки .textChanged к методу .setText в QLabel. Когда в QLineEdit меняется текст, он сразу будет поступать в QLabel (в метод .setText):

from PyQt6.QtWidgets import QApplication, QMainWindow, QLabel, QLineEdit, QVBoxLayout, QWidget

import sys


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        self.label = QLabel()

        self.input = QLineEdit()
        self.input.textChanged.connect(self.label.setText)

        layout = QVBoxLayout()
        layout.addWidget(self.input)
        layout.addWidget(self.label)

        container = QWidget()
        container.setLayout(layout)

        # Устанавливаем центральный виджет Window.
        self.setCentralWidget(container)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

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

Запускаем программу:

Введите текст в верхнем поле — он сразу появится в виде метки.

У большинства виджетов Qt есть доступные слоты, к которым подключается любой сигнал, возврощающий тот же тип, что он принимает. В документации по виджетам, в разделе Public Slots («Общедоступные слоты»), имеются слоты для каждого виджета. Посмотрите документацию для QLabel.

События

Любое взаимодействие пользователя с приложением Qt — это событие. Есть много типов событий, каждое из которых — это отдельный тип взаимодействия. В Qt события представлены объектами событий, в которые упакована информация о произошедшем. События передаются определённым обработчикам событий в виджете, где произошло взаимодействие.

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

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

Обработчик

Событие

mouseMoveEvent

Мышь переместилась

mousePressEvent

Кнопка мыши нажата

mouseReleaseEvent

Кнопка мыши отпущена

mouseDoubleClickEvent

Обнаружен двойной клик

Например, нажатие на виджет приведёт к отправке QMouseEvent в обработчик событий .mousePressEvent в этом виджете.

События можно перехватывать, создав подкласс и переопределив метод обработчика в этом классе. Их можно фильтровать, менять или игнорировать, передавая обычному обработчику путём вызова функции суперкласса методом super(). Они добавляются в класс главного окна следующим образом (в каждом случае в e будет получено входящее событие):

import sys

from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QApplication, QLabel, QMainWindow, QTextEdit


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.label = QLabel("Click in this window")
        self.setCentralWidget(self.label)

    def mouseMoveEvent(self, e):
        self.label.setText("mouseMoveEvent")

    def mousePressEvent(self, e):
        self.label.setText("mousePressEvent")

    def mouseReleaseEvent(self, e):
        self.label.setText("mouseReleaseEvent")

    def mouseDoubleClickEvent(self, e):
        self.label.setText("mouseDoubleClickEvent")


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

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

События перемещения мыши регистрируются только при нажатой кнопке. Чтобы это изменить, вызовите в окне self.setMouseTracking(True). События press («Нажатие кнопки»), click («Клика») и double-click («Двойного клика») срабатывают при нажатии кнопки. Событие release («Отпускание») срабатывает, только когда кнопка отпускается. Клик пользователя регистрируется обычно при нажатии кнопки мыши и её отпускании.

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

События управления мышью

В Qt все события управления мышью отслеживаются с помощью объекта QMouseEvent. При этом информация о событии считывается из следующих методов событий:

Метод

Возвращает

.button()

Конкретную кнопку, вызвавшую данное событие

.buttons()

Состояние всех кнопок мыши (флаги OR)

.position()

Относительную позицию виджета в виде целого QPoint .

Эти методы используются в обработчике событий, чтобы на разные события реагировать по-разному или полностью их игнорировать. Через методы позиционирования в виде объектов QPoint предоставляется глобальная и локальная (касающаяся виджета) информация о местоположении. Сведения о кнопках поступают с использованием типов кнопок мыши из пространства имён Qt. Например, в этом коде показаны разные реакции на нажатие левой, правой или средней кнопки мыши в окне:

    def mousePressEvent(self, e):
        if e.button() == Qt.LeftButton:
            # здесь обрабатываем нажатие левой кнопки
            self.label.setText("mousePressEvent LEFT")

        elif e.button() == Qt.MiddleButton:
            # здесь обрабатываем нажатие средней кнопки.
            self.label.setText("mousePressEvent MIDDLE")

        elif e.button() == Qt.RightButton:
            # здесь обрабатываем нажатие правой кнопки.
            self.label.setText("mousePressEvent RIGHT")

    def mouseReleaseEvent(self, e):
        if e.button() == Qt.LeftButton:
            self.label.setText("mouseReleaseEvent LEFT")

        elif e.button() == Qt.MiddleButton:
            self.label.setText("mouseReleaseEvent MIDDLE")

        elif e.button() == Qt.RightButton:
            self.label.setText("mouseReleaseEvent RIGHT")

    def mouseDoubleClickEvent(self, e):
        if e.button() == Qt.LeftButton:
            self.label.setText("mouseDoubleClickEvent LEFT")

        elif e.button() == Qt.MiddleButton:
            self.label.setText("mouseDoubleClickEvent MIDDLE")

        elif e.button() == Qt.RightButton:
            self.label.setText("mouseDoubleClickEvent RIGHT")

Идентификаторы кнопок определяются в пространстве имён Qt:

Код

Бинарное значение

Описание

Qt.NoButton

0 (000)

Кнопка не нажата, или событие не связано с нажатием кнопки

Qt.LeftButton

1 (001)

Левая кнопка нажата

Qt.RightButton

2 (010)

Правая кнопка нажата

Qt.MiddleButton

4 (100)

Средняя кнопка [обычно это колёсико мыши] нажата

В мышках для правшей положения левой и правой кнопок меняются местами, то есть при нажатии правой кнопки вернётся Qt.LeftButton. Иными словами, учитывать ориентацию мыши не нужно.

Контекстные меню

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

В примере ниже мы будем перехватывать событие .contextMenuEvent в QMainWindow. Это событие запускается всякий раз перед самым появлением контекстного меню, а передаётся событие с одним значением типа QContextMenuEvent.

Чтобы перехватить событие, просто переопределяем метод объекта с помощью нового метода с тем же именем. В нашем случае создаём метод в подклассе MainWindow с именем contextMenuEvent, и он будет получать все события этого типа:

import sys

from PyQt6.QtCore import Qt
from PyQt6.QtGui import QAction
from PyQt6.QtWidgets import QApplication, QLabel, QMainWindow, QMenu


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

    def contextMenuEvent(self, e):
        context = QMenu(self)
        context.addAction(QAction("test 1", self))
        context.addAction(QAction("test 2", self))
        context.addAction(QAction("test 3", self))
        context.exec(e.globalPos())


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

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

При передаче исходного положения в функцию exec оно должно соответствовать родительскому элементу, переданному при определении. В нашем случае в качестве родительского элемента передаётся self, поэтому используем глобальное положение. Для полноты картины нужно сказать, что создавать контекстные меню можно и при помощи сигналов:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.show()

        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.on_context_menu)

    def on_context_menu(self, pos):
        context = QMenu(self)
        context.addAction(QAction("test 1", self))
        context.addAction(QAction("test 2", self))
        context.addAction(QAction("test 3", self))
        context.exec(self.mapToGlobal(pos))

Что выбрать — решать только вам.

Иерархия событий

В PyQt каждый виджет — это часть двух различных иерархий: иерархии объектов в Python и иерархии макета в Qt. Реакция на события или их игнорирование влияет на поведение пользовательского интерфейса.

Перенаправление наследования Python

Часто бывает нужно перехватить событие, что-то с ним сделать, запустив при этом поведение обработки событий по умолчанию. Если объект унаследован от стандартного виджета, у него наверняка будет поведение по умолчанию. Запустить его можно, методом super() вызвав реализацию из суперкласса. Будет вызван именно суперкласс в Python, а не .parent() из PyQt:

def mousePressEvent(self, event):
    print("Mouse pressed!")
    super(self, MainWindow).contextMenuEvent(event)

Интерфейс ведёт себя как раньше, при этом мы добавили новое поведение, которое не вмешивается в прежнее.

Передача вверх по иерархии макета

Когда в приложение добавляется виджет, он также получает из макета другой родительский элемент. Чтобы найти родительский элемент виджета, надо вызвать функцию .parent(). Иногда эти родительские элементы указываются вручную (и часто автоматически), например для QMenu или QDialog. Когда в главное окно добавляется виджет, оно становится родительским элементом виджета.

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

Если первый виджет не может обработать событие, оно перейдёт к следующему по очереди родительскому виджету. Эта передача вверх по иерархии вложенных виджетов продолжится, пока событие не будет обработано или не достигнет главного окна. В обработчиках событий событие помечают как обработанное через вызов .accept():

    class CustomButton(QPushButton)
        def mousePressEvent(self, e):
            e.accept()

Вызвав в объекте события .ignore(), помечают его как необработанное. В этом случае событие будет передаваться по иерархии вверх:

    class CustomButton(QPushButton)
        def event(self, e):
            e.ignore()

Чтобы виджет пропускал события, вы можете спокойно игнорировать те, на которые каким-то образом реагировали. Аналогично вы можете отреагировать на одни события, заглушая другие. А продолжить изучение Python вы сможете на наших курсах:

  • Курс Python-разработчик

  • Профессия Fullstack-разработчик на Python

  • Курс «Python для веб-разработки»

Напомним также о книге автора этих уроков.


Виджеты

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

Краткое демо

Посмотрим на самые распространённые виджеты PyQt. Они создаются и добавляются в макет окна с помощью этого кода (работу макетов в Qt рассмотрим в следующем руководстве):

import sys

from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (
    QApplication,
    QCheckBox,
    QComboBox,
    QDateEdit,
    QDateTimeEdit,
    QDial,
    QDoubleSpinBox,
    QFontComboBox,
    QLabel,
    QLCDNumber,
    QLineEdit,
    QMainWindow,
    QProgressBar,
    QPushButton,
    QRadioButton,
    QSlider,
    QSpinBox,
    QTimeEdit,
    QVBoxLayout,
    QWidget,
)


# Подкласс QMainWindow для настройки основного окна приложения
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Widgets App")

        layout = QVBoxLayout()
        widgets = [
            QCheckBox,
            QComboBox,
            QDateEdit,
            QDateTimeEdit,
            QDial,
            QDoubleSpinBox,
            QFontComboBox,
            QLCDNumber,
            QLabel,
            QLineEdit,
            QProgressBar,
            QPushButton,
            QRadioButton,
            QSlider,
            QSpinBox,
            QTimeEdit,
        ]

        for w in widgets:
            layout.addWidget(w())

        widget = QWidget()
        widget.setLayout(layout)

        # Устанавливаем центральный виджет окна. Виджет будет расширяться по умолчанию,
        # заполняя всё пространство окна.
        self.setCentralWidget(widget)


app = QApplication(sys.argv)
window = MainWindow()
window.show()

app.exec()

Запускаем! Появится окно со всеми созданными виджетами:

Виджеты на Windows, Mac и Ubuntu Linux

Виджеты на Windows, Mac и Ubuntu Linux

Посмотрим внимательно на все эти виджеты:

Класс виджета

Описание виджета

QCheckbox

Чекбокс

QComboBox

Окно выпадающего списка

QDateEdit

Для редактирования даты и времени

QDateTimeEdit

Для редактирования даты и времени

QDial

Поворотный циферблат

QDoubleSpinbox

Спиннер для чисел с плавающей точкой

QFontComboBox

Список шрифтов

QLCDNumber

Довольно неприятный дисплей LCD

QLabel

Просто метка, не интерактивная

QLineEdit

Поле ввода со строкой

QProgressBar

Индикатор выполнения

QPushButton

Кнопка

QRadioButton

Переключаемый набор, в котором активен только один элемент

QSlider

Слайдер

QSpinBox

Спиннер для целых чисел

QTimeEdit

Поле редактирования времени

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

Разберём подробно самые используемые виджеты и поэкспериментируем с ними в простом приложении. Сохраните следующий код в файле app.py и, запустив его, убедитесь, что он работает:

import sys
from PyQt6.QtWidgets import (
    QMainWindow, QApplication,
    QLabel, QCheckBox, QComboBox, QListBox, QLineEdit,
    QLineEdit, QSpinBox, QDoubleSpinBox, QSlider
)
from PyQt6.QtCore import Qt

class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")


app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()

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

QLabel

Начнём с QLabel, одного из простейших виджетов в Qt. Он состоит из строчки текста и устанавливается в приложении. Текст задаётся аргументом QLabel:

widget = QLabel("Hello")

Или с помощью метода .setText():

widget = QLabel("1")  # Создана метка с текстом 1.
widget.setText("2")   # Создана метка с текстом 2.

Параметры шрифта, например его размер или выравнивание в виджете, настраиваются так:

class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")

        widget = QLabel("Hello")
        font = widget.font()
        font.setPointSize(30)
        widget.setFont(font)
        widget.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter)

        self.setCentralWidget(widget)

QLabel на Windows, Mac и Ubuntu Linux

QLabel на Windows, Mac и Ubuntu Linux

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

Флаг PyQt6 (полный код)

Поведение

Qt.AlignmentFlag.AlignLeft

Выравнивает по левому краю

Qt.AlignmentFlag.AlignRight

Выравнивает по правому краю

Qt.AlignmentFlag.AlignHCenter

Центрирует по горизонтали в доступном пространстве

Qt.AlignmentFlag.AlignJustify

Выравнивает текст в доступном пространстве

Флаги используются вместе с помощью каналов (|), но флаги вертикального и горизонтального выравнивания не применяются одновременно:

Флаг PyQt6 (полный код)

Поведение

Qt.AlignmentFlag.AlignTop

Выравнивается по верху

Qt.AlignmentFlag.AlignBottom

Выравнивается по низу

Qt.AlignmentFlag.AlignVCenter

Центрирует вертикально в доступном пространстве

align_top_left = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop

При этом для совместного применения двух флагов (не A & B) используется канал | с операцией логического «ИЛИ». Эти флаги — неперекрывающиеся битовые маски. Например, у Qt.AlignmentFlag.AlignLeft шестнадцатеричное значение 0x0001, а у Qt.AlignmentFlag.AlignBottom — 0x0040. С помощью операции логического «ИЛИ» получаем значение «внизу слева» — 0x0041. Этот принцип применим и ко всем остальным парам флагов Qt. Если вам что-то здесь непонятно, смело пропускайте эту часть и переходите к следующей. Только не забывайте использовать |. Наконец, есть флаг, с помощью которого выполняется выравнивание по центру в обоих направлениях одновременно:

Флаг PyQt6

Поведение

Qt.AlignmentFlag.AlignCenter

Центрирует горизонтально и вертикально.

Как ни странно, QLabel используется и для показа изображения с помощью .setPixmap(). Этот метод принимает QPixmap, создаваемый передачей имени файла изображения в QPixmap. Среди примеров из этой книги есть файл otje.jpg, который отображается в окне так:

widget.setPixmap(QPixmap('otje.jpg'))

Кот Отье

Кот Отье

Какая мордочка! По умолчанию изображение масштабируется, сохраняя соотношение ширины и высоты. Если нужно растянуть и подогнать его по размерам окна, установите .setScaledContents(True) в QLabel:

widget.setScaledContents(True)

QCheckBox

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

class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")

        widget = QCheckBox()
        widget.setCheckState(Qt.CheckState.Checked)

        # Включение трёх состояний: widget.setCheckState(Qt.PartiallyChecked)
        # Или: widget.setTriState(True)
        widget.stateChanged.connect(self.show_state)

        self.setCentralWidget(widget)


    def show_state(self, s):
        print(s == Qt.CheckState.Checked)
        print(s)

QCheckBox на Windows, Mac & Ubuntu Linux

QCheckBox на Windows, Mac & Ubuntu Linux

Состояние чекбокса устанавливается программно с помощью .setChecked или .setCheckState. Первый принимает True или False, то есть чекбокс с галочкой или без неё соответственно. В случае с .setCheckState с помощью флага пространства имён Qt также указывается конкретное состояние чекбокса:

Флаг PyQt6 (Полный код)

Состояние

Qt.CheckState.Unchecked

Элемент не отмечен

Qt.CheckState.PartiallyChecked

Элемент отмечен частично

Qt.CheckState.Checked

Элемент отмечен

Чекбокс, поддерживающий состояние частичной отмеченности (Qt.CheckState.PartiallyChecked) галочками, обычно называют «имеющим третье состояние», т. е. он и отмечен, и не отмечен. Чекбокс в этом состоянии часто отображается в виде серого флажка и используется в иерархических структурах чекбоксов, где дочерние чекбоксы связаны с родительскими.

Установив значение Qt.CheckState.PartiallyChecked, вы переведёте чекбокс в третье состояние. То же самое, но без перевода текущего состояния в состояние частичной отмеченности галочками, делается с помощью .setTriState(True).

При запуске скрипта номер текущего состояния отображается в виде значения целочисленного типа int. Причём состоянию, отмеченному галочками, соответствует 2, не отмеченному — 0, а состоянию частичной отмеченности — 1. Запоминать эти значения не нужно: К примеру, переменная пространства имён Qt.Checked равна 2. Это значение соответствующих флагов состояния. То есть вы можете проверить состояние с помощью state == Qt.Checked.

QComboBox

QComboBox — это выпадающий список, закрытый по умолчанию, с кнопкой, чтобы открыть его. Можно выбрать один элемент из списка, при этом выбранный в данный момент элемент отображается в виджете в виде метки. Поле со списком подходит для выбора варианта из длинного списка вариантов. Такое поле используется при выборе начертания или размера шрифта в текстовых редакторах. В Qt для выбора шрифта есть специальное поле со списком шрифтов — QFontComboBox.

В QComboBox можно добавлять элементы, передавая список строк в .addItems(). Элементы добавляются в порядке расположения соответствующих элементам строк кода.

class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")

        widget = QComboBox()
        widget.addItems(["One", "Two", "Three"])

        # Отправляет текущий индекс (позицию) выбранного элемента.
        widget.currentIndexChanged.connect( self.index_changed )

        # Есть альтернативный сигнал отправки текста.
        widget.textChanged.connect( self.text_changed )

        self.setCentralWidget(widget)


    def index_changed(self, i): # i — это int
        print(i)

    def text_changed(self, s): # s — это str
        print(s)

QComboBox на Windows, Mac и Ubuntu Linux

QComboBox на Windows, Mac и Ubuntu Linux

Сигнал .currentIndexChanged срабатывает при обновлении выбранного в данный момент элемента, индекс которого в списке передаётся по умолчанию. Кроме того, есть сигнал .currentTextChanged, при срабатывании которого вместо индекса предоставляется метка выбранного в данный момент элемента. Это часто оказывается полезнее.

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

widget.setEditable(True)

Чтобы определить, как обрабатываются вставляемые значения, устанавливается флаг. Флаги хранятся в самом классе QComboBox. Вот их список:

Флаг PyQt6 (полный код)

Поведение

QComboBox.InsertPolicy.NoInsert

Не вставлять

QComboBox.InsertPolicy.InsertAtTop

Вставить как первый элемент

QComboBox.InsertPolicy.InsertAtCurrent

Заменить текущий выбранный элемент

QComboBox.InsertPolicy.InsertAtBottom

Вставить после последнего элемента

QComboBox.InsertPolicy.InsertAfterCurrent

Вставить после текущего элемента

QComboBox.InsertPolicy.InsertBeforeCurrent

Вставить перед текущим элементом

QComboBox.InsertPolicy.InsertAlphabetically

Вставить в алфавитном порядке

Чтобы использовать их, применяется флаг:

widget.setInsertPolicy(QComboBox.InsertPolicy.InsertAlphabetically)

Кроме того, можно ограничить количество элементов поля, например при помощи .setMaxCount:

widget.setMaxCount(10)

Подробнее о QComboBox рассказывается здесь.

QListWidget

Этот виджет похож на QComboBox, но варианты здесь представлены в виде прокручиваемого списка элементов и возможен выбор нескольких элементов одновременно. В QListWidget есть сигнал currentItemChanged для отправки QListItem (элемента виджета списка) и сигнал currentTextChanged для отправки текста текущего элемента:

class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")

        widget = QListWidget()
        widget.addItems(["One", "Two", "Three"])

        widget.currentItemChanged.connect(self.index_changed)
        widget.currentTextChanged.connect(self.text_changed)

        self.setCentralWidget(widget)


    def index_changed(self, i); # i — не индекс, а сам QList
        print(i.text())

    def text_changed(self, s): # s — это строка
        print(s)

QListWidget на Windows, Mac и Ubuntu Linux

QListWidget на Windows, Mac и Ubuntu Linux

QLineEdit

Виджет QLineEdit — это простое однострочное текстовое поле для редактирования вводимых пользователями данных. Он используется для полей, где нет ограничений на входные данные. Например, при вводе адреса электронной почты или имени компьютера:

class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")

        widget = QLineEdit()
        widget.setMaxLength(10)
        widget.setPlaceholderText("Enter your text")

        #widget.setReadOnly(True) # раскомментируйте, чтобы сделать доступным только для чтения

        widget.returnPressed.connect(self.return_pressed)
        widget.selectionChanged.connect(self.selection_changed)
        widget.textChanged.connect(self.text_changed)
        widget.textEdited.connect(self.text_edited)

        self.setCentralWidget(widget)


    def return_pressed(self):
        print("Return pressed!")
        self.centralWidget().setText("BOOM!")

    def selection_changed(self):
        print("Selection changed")
        print(self.centralWidget().selectedText())

    def text_changed(self, s):
        print("Text changed...")
        print(s)

    def text_edited(self, s):
        print("Text edited...")
        print(s)

QLineEdit на Windows, Mac и Ubuntu Linux

QLineEdit на Windows, Mac и Ubuntu Linux

Как показано в этом коде, при однострочном редактировании вы можете установить максимальную длину текста.

В QLineEdit есть ряд сигналов для различных событий редактирования, в том числе при нажатии клавиши return (пользователем), когда пользователь изменил свой выбор. Также есть два сигнала редактирования: один — на случай, когда текст в поле отредактирован, другой — когда он изменён. Здесь различаются два вида изменений: пользовательские и выполненные программой. Сигнал textEdited отправляется только при редактировании текста пользователем.

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

widget.setInputMask('000.000.000.000;_')

Это позволяет использовать серию трёхзначных чисел, разделённых точками, для проверки адресов IPv4.

QSpinBox и QDoubleSpinBox

QSpinBox — это небольшое поле ввода чисел со стрелками для изменения значения. Оно поддерживает целые числа, а QDoubleSpinBox — числа с плавающей точкой:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        widget = QSpinBox()
        # Или: widget = QDoubleSpinBox()

        widget.setMinimum(-10)
        widget.setMaximum(3)
        # Или: widget.setRange(-10,3)

        widget.setPrefix("$")
        widget.setSuffix("c")
        widget.setSingleStep(3)  # Или, например, 0.5 в QDoubleSpinBox
        widget.valueChanged.connect(self.value_changed)
        widget.textChanged.connect(self.value_changed_str)

        self.setCentralWidget(widget)

    def value_changed(self, i):
        print(i)

    def value_changed_str(self, s):
        print(s)

Запустив код, вы увидите поле для ввода чисел. Значение в нём показывает префиксные и постфиксные блоки и ограничено диапазоном от +3 до –10:

QSpinBox на Windows, Mac и Ubuntu Linux

QSpinBox на Windows, Mac и Ubuntu Linux

В этом коде показан разнообразный функционал виджета. Чтобы задать диапазон допустимых значений, используются setMinimum и setMaximum. Одновременно они устанавливаются с помощью SetRange. Аннотация типов значений поддерживается префиксами и суффиксами, добавляемыми к числу. Например, для обозначений валюты или денежных единиц используются .setPrefix и .setSuffix соответственно.

Нажав на стрелки «вверх» и «вниз» на виджете, можно увеличить или уменьшить значение на величину, устанавливаемую с помощью .setSingleStep. Но это не повлияет на допустимые для виджета значения.

В QSpinBox и QDoubleSpinBox есть сигнал .valueChanged, срабатывающий при изменении их значений. С помощью необработанного сигнала .valueChanged отправляется числовое значение (целочисленного типа int или типа числа с плавающей точкой float), а с помощью .textChanged — значение в виде строки, включающей символы префикса и суффикса.

QSlider

QSlider — это ползунок, очень схожий по внутреннему функционалу с QDoubleSpinBox. Текущее значение представлено не числами, а в виде маркера ползунка, располагающегося по всей длине виджета. Этот виджет удобен для указания значения в промежутке между максимумом и минимумом, где абсолютная точность не требуется. Чаще всего этот тип виджетов используется при регулировке громкости. Есть и другие сигналы: .sliderMoved срабатывает при перемещении ползунка а .sliderPressed — при нажатии на ползунок:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        widget = QSlider()

        widget.setMinimum(-10)
        widget.setMaximum(3)
        # Или: widget.setRange(-10,3)

        widget.setSingleStep(3)

        widget.valueChanged.connect(self.value_changed)
        widget.sliderMoved.connect(self.slider_position)
        widget.sliderPressed.connect(self.slider_pressed)
        widget.sliderReleased.connect(self.slider_released)

        self.setCentralWidget(widget)

    def value_changed(self, i):
        print(i)

    def slider_position(self, p):
        print("position", p)

    def slider_pressed(self):
        print("Pressed!")

    def slider_released(self):
        print("Released")

Запустив код, вы увидите ползунок. Переместите его, чтобы изменить значение:

QSlider на Windows, Mac и Ubuntu Linux

QSlider на Windows, Mac и Ubuntu Linux

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

widget.QSlider(Qt.Orientiation.Vertical)

Или так:

widget.QSlider(Qt.Orientiation.Horizontal)

QDial

Наконец, QDial — это круговой виджет, схожий по функционалу с механическим ползунком из реального мира. Красиво, но с точки зрения пользовательского интерфейса не очень удобно. Он часто используется в аудиоприложениях:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        widget = QDial()
        widget.setRange(-10, 100)
        widget.setSingleStep(0.5)

        widget.valueChanged.connect(self.value_changed)
        widget.sliderMoved.connect(self.slider_position)
        widget.sliderPressed.connect(self.slider_pressed)
        widget.sliderReleased.connect(self.slider_released)

        self.setCentralWidget(widget)

    def value_changed(self, i):
        print(i)

    def slider_position(self, p):
        print("position", p)

    def slider_pressed(self):
        print("Pressed!")

    def slider_released(self):
        print("Released")

Запустив код, вы увидите круговой ползунок. Поверните его, чтобы выбрать число:

QDial на Windows, Mac и Ubuntu Linux

QDial на Windows, Mac и Ubuntu Linux

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

  • Курс Python-разработчик

  • Профессия Fullstack-разработчик на Python

  • Курс «Python для веб-разработки»

Узнайте подробности здесь.

Профессии и курсы

Все курсы > Программирование на Питоне > Занятие 13

Сегодня мы сделаем шаг назад и посмотрим в целом, что такое программа на Питоне.

Способ 1. Писать код в облаке в Google Colab.

Способ 2. Написать программу в отдельном файле (скрипте) с расширением .py и передать этот код специальному интерпретатору для исполнения.

Способ 3. Установить Jupyter Notebook (локальный аналог Google Colab).

программа на Питоне, способы исполнения кода

С первым способом мы уже познакомились в рамках вводного курса. Сегодня мы займемся написанием программы в отдельном файле, а в следующий раз — изучим Jupyter Notebook.

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

Установка Питона на Windows

Прежде чем мы начнем писать программу, нам нужно установить библиотеки (libraries) и интерпретатор (interpreter) для обработки кода.

Шаг 1. Проверить, установлен ли Питон на вашем компьютере

Для этого вначале нажмите клавишу Windows и клавишу R на клавиатуре.

клавиши Windows + R

В появившемся окне «Выполнить» введите
cmd и нажмите Enter.

окно "Выполнить" в Windows

Появится так называемая «командная строка» (Command Line Promt) — отдельная программа, позволяющая взаимодействовать с Windows не графически (как мы привыкли делать), а через текстовые команды.

командная строка (Command Line Promt)

Теперь введите
python —version. Если Питон установлен, то программа сообщит текущую версию. В противном случае появится вот такая запись.

проверка, установлен ли Питон

Если Питон не установлен, переходите к шагу 2. В случае если вы его уже установили, переходите сразу к шагу 3.

Шаг 2. Скачать Питон с официального сайта

Перейдем на сайт www.python.org/dowloads/⧉ и скачаем, среди прочего, базовый функционал Питона, а также интерпретатор для Windows, который позволит нам исполнять написанный нами код.

скачивание Питона с официального сайта

После скачивания и запуска файла откроется мастер установки.

добавление Питона в переменную PATH

Нижняя галочка добавит Питон в переменную окружения PATH. Благодаря этому мы сможем исполнять код на Питоне напрямую из командной строки. Мы разберем как это делается уже на следующем шаге.

Снова проверим установку Питона на компьютере, повторив действия Шага 1. В командной строке должна появиться установленная на данный момент версия Питона.

проверка версии Питона

Шаг 3. Запустить Питон из командной строки

Теперь давайте введем в командной строке команду
py. Должны появиться символы
>>>.

запуск Питона из командной строки

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

Напишем классическую фразу:

При корректном исполнении кода фраза будет выведена на экран.

написание и исполнение кода на Питоне в интерактивном режиме

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

Поэтому выйдем из интерактивного режима с помощью команды
quit() или
exit(), закроем окно командной строки и перейдем к созданию программы на Питоне.

Создание программы на Питоне

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

Шаг 1. Скачать редактор кода

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

В принципе, если вы работаете на Windows, то можете воспользоваться и «Блокнотом» (Notepad), который уже установлен в этой операционной системе (MS Word использовать не стоит). Достаточно написать в нем код, сохранить файл и изменить расширение с .txt на .py.

Если вы не видите расширения файлов, в «Проводнике» нажмите на вкладку «Вид» и поставьте галочку напротив «Расширения имен файлов».

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

  • Notepad++⧉
  • Sublime⧉
  • Atom⧉

На сегодняшнем занятии мы будем использовать Atom.

Редактор Atom

После установки и запуска редактора Atom закройте ненужные вкладки и нажмите FileNew File.

Затем, чтобы сообщить редактору, что мы хотим писать код на Питоне, сохраним этот файл с расширением .py. Для этого нажмем FileSave As и сохраним файл, например, под именем script.py на Рабочий стол.

интерфейс редактора Atom

Благодаря расширению .py Atom будет знать, что в файле script.py мы собираемся писать код на Питоне.

Шаг 2. Написать программу на Питоне

Первой программой будет алгоритм линейного поиска (linear search algorithm). Этот алгоритм проверяет наличие числа в массиве путем простого перебора всех значений от первого до последнего.

алгоритм линейного поиска

Напишем такую программу в редакторе Atom и сохраним файл script.py.

# возьмем массив,

arr = [3, 7, 0, 2, 5]

# в котором нам нужно найти число 2

x = 2

# в цикле пройдемся по индексу массива

for i in range(len(arr)):

    # если искомое число находится на этом индексе

    if (arr[i] == x):

        # выведем индекс

        print(i)

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

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

Шаг 3. Запустить программу из командной строки

Запустим этот код с помощью командной строки.

  1. Откроем командную строку через клавиши Window + R
    cmd. Перейдем на Рабочий стол (напомню, файл script.py мы сохранили именно туда) с помощью команды
    cd Desktop.

Команда cd (change directory) позволяет перейти в другую папку, а Desktop — это Рабочий стол, то есть название той папки, куда мы хотим перейти. В результате командная строка должна выглядеть вот так:

смена папки в командной строке

  1. Теперь просто введите script.py. Так мы вызовем интерпретатор и исполним код.

исполнение программы на Питоне в командной строке

Все, наша первая программа на Питоне готова.

Установка библиотек

Как уже было сказано, по умолчанию, с сайта www.python.org устанавливается лишь базовый функционал. Если мы хотим использовать, например, библиотеку Numpy или библиотеку Matplotlib нам нужно установить их отдельно. В этом нам поможет программа pip.

Программа pip

pip — это программа, которая помогает устанавливать (обновлять и удалять) дополнительные библиотеки на вашем компьютере. По сути эта программа связывается с репозиторием (хранилищем) пакетов/библиотек Python Package Index или PyPI (pypi.org⧉) и скачивает запрашиваемые файлы.

Все действия осуществляются из командной строки.

Если вы устанавливали Питон в соответствии с приведенной выше инструкцией, то pip уже присутствует на вашем компьютере. Проверить это можно с помощью команды
pip —version.

проверка версии программы pip

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

список установленных библиотек Питона

Установка библиотеки Numpy через pip install

Установим библиотеку Numpy. Для этого введем в командной строке
pip install numpy.

установка Numpy через pip

Проверить установку отдельного пакета можно с помощью команды
pip show numpy.

проверка версии Numpy

Использование установленной библиотеки

Теперь мы можем использовать установленную библиотеку Numpy внутри командной строки. Вначале перейдем в интерактивный режим с помощью команды py. После этого построчно (каждый раз нажимая Enter) введем следующие команды:

import numpy as np

arr=np.array([1, 2, 3])

type(arr)

использование установленной через pip библиотеки

Как мы видим, в результате исполнения этого кода Питон успешно создал массив Numpy.

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

Создатели библиотек периодически вносят в них обновления, и эти обновления полезно скачивать на свой компьютер. Воспользуйтесь следующей командой:
pip install —upgrade numpy.

обновление библиотек через pip

Для удаления пакета введите команду
pip uninstall numpy.

удаление библиотек через pip

В процессе удаления будет нужно нажать Y + Enter для подтверждения операции. Другие библиотеки устанавливаются, обновляются и удаляются точно так же.

Модуль в Питоне

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

Модуль в Питоне — это программа на Питоне (файл с расширением .py), которую мы можем использовать в других программах с помощью команды import.

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

Создание собственного модуля

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

Алгоритм линейного поиска

Алгоритм линейного поиска у нас уже готов. Достаточно «обернуть» его в функцию.

# объявим функцию linear()

def linear(arr, x):

    for i in range(len(arr)):

        if arr[i] == x:

            return i

Теперь перейдем к бинарному поиску.

Алгоритм бинарного поиска

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

телефонная книга

Если фамилия этого человека начинается с буквы А, то мы довольно быстро найдем его номер, используя уже известный нам алгоритм линейного поиска. А если он Яковлев? Линейному поиску придется перебрать все буквы от А до Я.

Бинарный же поиск действует иначе. Вначале мы открываем книгу посередине, скажем, на букве П.

алгоритм бинарного поиска: поиск по словарю

После этого мы посмотрим, находится ли буква Я в первой или во второй половине книги. Так как Я очевидно находится во второй половине справочника, мы разобьем пополам вторую половину. И снова посмотрим справа искомая буква или слева.

алгоритм бинарного поиска: поиск по словарю 2

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

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

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

# создадим класс BinarySearch

class BinarySearch:

    # метод __init__() пропишем, но оставим пустым

    def __init__(self):

        pass

    # метод .srt() будет сортировать список чисел

    def srt(self, arr):

        # для этого мы используем функцию sorted()

        arr = sorted(arr)

        return arr

    # сам бинарный поиск будет выполняться через метод .check()

    def check(self, arr, x):

        # вначале зададим индексы первого и последнего значений

        # отсортированного списка

        low, high = 0, len(arr)1

        # цикл while будет длиться до тех пор, пока индекс последнего значения

        # больше или равен первому

        while low <= high:

            # найдем индекс среднего значения списка

            mid = low + (high low) // 2

            # если число с этим индексом равно искомому,

            if arr[mid] == x:

                # вернем этот индекс

                return mid

            # если меньше искомого (число «справа» от середины)

            elif arr[mid] < x:

                # новым нижним индексом будет «середина + 1»

                low = mid + 1

            # если больше искомого (число «слева» от середины)

            else:

                # новым верхним индексом будет «середина — 1»

                high = mid 1

        # если число так и не найдено, вернем -1

        mid = 1

        return mid

Хотя это уводит нас в сторону от темы сегодняшнего занятия, поясню код нахождения индекса среднего значения списка.

mid = low + (high low) // 2

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

Однако первый вариант расчета индекса среднего значения позволяет избежать переполнения памяти (overflow) при использовании слишком больших значений.

Также замечу, что мы используем оператор целочисленного деления
//, потому что в Питоне результатом обычного деления является число с плавающей точкой (float). Индекс же таким числом быть не может.

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

def linear(arr, x):

    for i in range(len(arr)):

        if arr[i] == x:

            return i

class BinarySearch:

    def __init__(self):

        pass

    def srt(self, arr):

        arr = sorted(arr)

        return arr

    def check(self, arr, x):

        low, high = 0, len(arr)1

        while low <= high:

            mid = low + (high low) // 2

            if arr[mid] == x:

                return mid

            elif arr[mid] < x:

                low = mid + 1

            else:

                high = mid 1

        mid = 1

        return mid

Документирование кода с помощью docstrings

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

Одновременно в Питоне существуют так называемые строки документации (docstrings). Они используются для описания работы функции, метода, класса или модуля. Доступ к ним можно получить через атрибут __doc__ или функцию help().

В чем основные особенности создания docstrings?

  • Docstrings заключаются в тройные одинарные или двойные кавычки
  • Их следует располагать сразу после объявления функции, метода, класса или модуля

Добавим соответствующие docstrings в только что созданный нами модуль и параллельно разберем основные принципы написания документации. Начнем с модуля в целом.

Строки документации для модуля в Питоне

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

«»»

Модуль для поиска элементов в массиве чисел.

============================================

Classes

——-

BinarySearch

Functions

———

linear

«»»

Строки документации для функции описывают саму функцию, параметры и возвращаемое значение. Напишем документацию к функции linear().

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

def linear(arr, x):

    «»»Выполняет линейный поиск по массиву чисел.

    Parameters

    ———-

    arr : {list, ndarray}

        Массив чисел, по которому выполняется поиск.

    x : int

        Искомое число.

    Returns

    ——-

    i : int

        Индекс искомого числа, если оно присутствует в массиве.

    «»»

    for i in range(len(arr)):

        if arr[i] == x:

            return i

Строк документации для класса описывают сам класс, а также перечисляют доступные атрибуты и методы. Каждый метод внутри класса сопровождается отдельной документацией.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

class BinarySearch:

    «»»Бинарный поиск по массиву чисел.

    «»»

    def __init__(self):

        pass

    def srt(self, arr):

        «»»Сортирует массив чисел в возрастающем порядке.

        Parameters

        ———-

        arr : {list, ndarray}

            Массив для сортировки.

        Returns

        ——-

        arr : {list, ndarray}

            Массив, отсортированный в возрастающем порядке.

        «»»

        arr = sorted(arr)

        return arr

    def check(self, arr, x):

        «»»Проверяет наличие числа в массиве c помощью алгоритма бинарного поиска.

        Parameters

        ———-

        arr : {list, numpy array}

            Массив чисел, по которому выполняется поиск.

        x : int

            Искомое число.

        Returns

        ——-

        mid : int

            Индекс числа в отсортированном по возрастанию массиве чисел.

            Возвращает -1, если число не найдено.

        «»»

        low, high = 0, len(arr)1

        while low <= high:

            mid = low + (high low) // 2

            if arr[mid] == x:

                return mid

            elif arr[mid] < x:

                low = mid + 1

            else:

                high = mid 1

        mid = 1

        return mid

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

«»»

Модуль для поиска элементов в массиве чисел.

============================================

Classes

——-

BinarySearch

Functions

———

linear

«»»

def linear(arr, x):

    «»»Выполняет линейный поиск по массиву чисел.

    Parameters

    ———-

    arr : {list, ndarray}

        Массив чисел, по которому выполняется поиск.

    x : int

        Искомое число.

    Returns

    ——-

    i : int

        Индекс искомого числа, если оно присутствует в массиве.

    «»»

    for i in range(len(arr)):

        if arr[i] == x:

            return i

class BinarySearch:

    «»»Бинарный поиск по массиву чисел.

    «»»

    def __init__(self):

        pass

    def srt(self, arr):

        «»»Сортирует массив чисел в возрастающем порядке.

        Parameters

        ———-

        arr : {list, ndarray}

            Массив для сортировки.

        Returns

        ——-

        arr : {list, ndarray}

            Массив, отсортированный в возрастающем порядке.

        «»»

        arr = sorted(arr)

        return arr

    def check(self, arr, x):

        «»»Проверяет наличие числа в массиве c помощью алгоритма бинарного поиска.

        Parameters

        ———-

        arr : {list, numpy array}

            Массив чисел, по которому выполняется поиск.

        x : int

            Искомое число.

        Returns

        ——-

        mid : int

            Индекс числа в отсортированном по возрастанию массиве чисел.

            Возвращает -1, если число не найдено.

        «»»

        low, high = 0, len(arr)1

        while low <= high:

            mid = low + (high low) // 2

            if arr[mid] == x:

                return mid

            elif arr[mid] < x:

                low = mid + 1

            else:

                high = mid 1

        mid = 1

        return mid

Замечу, что в данном случае мы использовали стиль документирования Numpy (NumPy documentation style). Он используется во многих известных пакетах: NumPy, SciPy, Pandas или, например, Scikit-Learn. При этом существуют и другие стили документирования.

Сохраним этот файл под именем mymodule.py. Все, наш модуль готов. Если у вас не получилось создать этот файл, вы можете скачать его по ссылке ниже.

Создание документации с помощью Pyment

Дополнительно замечу, что шаблон или «каркас» документации можно создать с помощью специального пакета Pyment. Для этого:

  1. Скачайте пакет Pyment через
    pip install pyment
  2. Убедитесь, что командная строка указывает на ту папку, в которой находится ваш модуль mymodule.py (например, на Рабочий стол)
  3. Введите команду
    pyment -w -o numpydoc mymodule.py. В данном случае вы буквально просите pyment создать документацию в стиле Numpy в файле под названием mymodule.py
  4. Откройте файл в редакторе кода и начинайте заполнять шаблон.

Загрузка и импорт модуля в Google Colab

Откроем ноутбук к этому занятию⧉

Давайте подгрузим файл mymodule.py в сессионное хранилище Google Colab.

подгрузка собственного модуля на Питоне в Google Colab

Теперь мы можем работать с этим модулем так, как мы работали с функциями модуля random или классами библиотеки sklearn.

Вначале импортируем функцию linear() из модуля mymodule.

from mymodule import linear

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

arr = [3, 7, 0, 2, 5]

target = 2

Вызовем функцию linear() и передадим ей список и целевое значение в качестве аргументов.

Теперь возьмем список большего размера и другое целевое значение.

arr = [9, 3, 343, 5, 8, 1, 20111, 32, 11, 6, 4]

target = 9

Импортируем модуль mymodule под псевдонимом mm.

Воспользуемся бинарным поиском. Для этого вначале создадим объект класса BinarySearch и поместим его в переменную src.

Прежде чем выполнить поиск нам необходимо отсортировать список чисел. Вызовем метод .srt() класса BinarySearch.

# передадим методу .srt() список arr для сортировки

sorted_arr = srch.srt(arr)

# посмотрим на результат

sorted_arr

[1, 3, 4, 5, 6, 8, 9, 11, 32, 343, 20111]

Теперь воспользуемся методом .check(), чтобы проверить, присутствует ли в списке число девять.

# напомню, что индекс числа 9 мы будем отсчитывать с нуля

src.check(sorted_arr, target)

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

Просмотр документации модуля

Вначале выведем документацию модуля в целом.

Модуль для поиска элементов в массиве чисел.

============================================

Classes

——-

BinarySearch

Functions

———

linear

Посмотрим на функию linear().

Выполняет линейный поиск по массиву чисел.

    Parameters

    ———-

    arr : {list, ndarray}

        Массив чисел, по которому выполняется поиск.

    x : int

        Искомое число.

    Returns

    ——-

    i : int

        Индекс искомого числа, если оно присутствует в массиве.

И класс BinarySearch.

print(mm.BinarySearch.__doc__)

Бинарный поиск по массиву чисел.

Мы также можем посмотреть документацию отдельного метода внутри класса.

print(mm.BinarySearch.srt.__doc__)

Сортирует массив чисел в возрастающем порядке.

        Parameters

        

        arr : {list, ndarray}

            Массив для сортировки.

        Returns

        

        arr : {list, ndarray}

            Массив, отсортированный в возрастающем порядке.

Напомню, что документацию можно также посмотреть с помощью функции help().

Импорт собственного модуля в командной строке

Модуль в Питоне не обязательно подгружать в Google Colab, его также можно импортировать локально в командной строке.

Когда в интерактивном режиме мы пытаемся импортировать модуль с помощью команды import, Питон начинает искать этот модуль в конкретных папках. Посмотреть, что это за папки можно с помощью встроенного в базовый функционал модуля sys. В интерактивном режиме (команда
py) последовательно введите следующие команды.

содержание переменной sys.path

Обратите внимание, что первой в списке
[»] указана текущая папка.

Если ваш модуль не находится в одной из этих папок, импортировать его не получится. Здесь есть два варианта: либо переместить файл в одну из указанных папок, либо добавить новую папку в переменную path.

Способ 1. Переместить файл в папку из списка

Текущая папка будет иметь адрес, похожий на
C:\Users\user (замените user на имя вашей учетной записи). Введите этот адрес в Проводнике.

ввод адреса в Проводнике на Windows

Переместите туда наш модуль mymodule.py. Теперь войдем в интерактивный режим (команда
py) и импортируем модуль с помощью команды
import. После этого создадим массив, целевую переменную и вызовем функцию linear().

import mymodule

arr = [3, 7, 0, 2, 5]

target = 2

mymodule.linear(arr, target)

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

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

Способ 2. Добавить новый путь (папку) в переменную path

Добавим Рабочий стол в список sys.path. Для этого прекрасно подойдет метод .append(), который мы использовали для обычных питоновских списков.

Например, добавим Desktop (Рабочий стол).

добавление папки в переменную sys.path

Не забудьте заменить user на имя пользователя на вашем компьютере, а также обратите внимание на двойные обратные косые черты
\\ в абсолютном пути к папке Desktop.

содержание переменной sys.path после добавления папки

Мы готовы импортировать наш модуль с Рабочего стола. Вернем файл mymodule.py на Рабочий стол, войдем в интерактивный режим (команда
py и последовательно введем код ниже.

import mymodule

arr = [3, 7, 0, 2, 5]

target = 2

mymodule.linear(arr, target)

импорт модуля из добавленной в sys.path папки

Нам снова удалось импортировать необходимую нам функцию linear().

Интерпретация и компиляция

Небольшое отступление от темы занятия. В самом начале мы сказали, что вместе с базовым функционалом Питона мы импортируем еще и интерпретатор. Давайте, разберемся, что это такое.

Как вы помните, компьютер понимает только нули и единицы, но никак не код на Питоне. Перевести понятный человеку язык программирования на машинный можно двумя способами: через компилятор (compiler) и через интерпретатор (interpreter).

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

Компилятор

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

Компилятор берет файл с понятным человеку исходным кодом, переводит его в нули и единицы и сохраняет получившийся машинный код в исполняемом (executable) файле (на Windows — это файл с расширением .exe).

компилятор

После этого мы можем запустить файл .exe и увидеть результат работы программы.

Интерпретатор

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

интерпретатор

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

Как следствие, языки делятся на компилируемые и интерпретируемые. Питон относится к интерпретируемым языкам, а, например, С — к компилируемым.

Впрочем, программа на Питоне может быть скомпилирована, например, с помощью пакета PyInstaller.

Кроме того, возможно вы обратили внимание, что когда мы вызывали модуль mymodule в командной строке, то Питон автоматически создал папку под названием __pycache__.pyc. В ней содержится скомпилированный байт-код программы (промежуточный код между Питоном и машинным языком), который ускоряет последующий запуск нашего модуля.

Пакет в Питоне

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

программа на Питоне (скрипт) - модуль - пакет

Примечание. Некоторые пакеты (например, Numpy или Pandas) принято называть библиотеками. При этом с технической точки зрения пакет и библиотека — это одно и то же.

Создание собственного пакета

В качестве упражнения создадим несложный пакет на Питоне и поместим его в тестовый репозиторий TestPyPI. Это своего рода «песочница», в которой можно научиться создавать пакеты перед их загрузкой в «большой» репозиторий PyPI.

Обратите внимание, PyPI и TestPyPI — это разные сайты, для которых требуются разные учетные записи.

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

Шаг 1. Создание учетной записи

В первую очередь зарегистрируйтесь на сайте https://test.pypi.org/⧉.

Шаг 2. Создание файлов

Теперь создайте пакет example_package (по сути, набор папок и файлов) со следующей структурой.

base/

└── src/

    └── example_package/

        ├── __init__.py

        └── example_module.py

В пакет мы поместим модуль example_module.py. В модуле объявим функцию для возведения числа в квадрат square().

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

«»»

Модуль для выполнения арифметических операций.

============================================

Functions

———

square

«»»

def square(number):

    «»»Возводит число в квадрат.

    Parameters

    ———-      

    number : int

        Возводимое во вторую степень число.

    Returns

    ——-

    int

        Квадрат числа.

    «»»

    return number ** 2

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

Все последующие инструкции в командной строке будут выполняться из папки base/.

Например, если папка base/ находится на Рабочем столе, то перейти в нее можно с помощью команды
cd Desktop\base.

переход в папку base в командной строке

Дополнительные файлы

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

base/

├── LICENSE.txt

├── pyproject.toml

├── README.md

├── setup.py

└── src/

    └── example_package/

        ├── __init__.py

        └── example_module.py

  1. В файл pyproject.toml поместим следующий код:

[build-system]

requires = [«setuptools>=42»]

build-backend = «setuptools.build_meta»

build-system.requires указывает на пакеты, необходимые для создания дистрибутива (то есть готового к распротранению пакета),
build-system.build-backend прописывает, какой объект будет использован для его создания.

  1. В setuptools.py содержится информация о пакете. Там же прописывается, какие файлы следует использовать при создании дистрибутива.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

import setuptools

with open(«README.md», «r», encoding=«utf-8») as fh:

    long_description = fh.read()

setuptools.setup(

    name=«example-package-DmitryMakarov»,

    version=«0.0.1»,

    author=«Dmitry Makarov»,

    author_email=«dm.v.makarov@gmail.com»,

    description=«Simple arithmetic package»,

    long_description=long_description,

    long_description_content_type=«text/markdown»,

    url=«https://github.com»,

    project_urls={

        «Bug Tracker»: «https://github.com»,

    },

    classifiers=[

        «Programming Language :: Python :: 3»,

        «License :: OSI Approved :: MIT License»,

        «Operating System :: OS Independent»,

    ],

    package_dir={«»: «src»},

    packages=setuptools.find_packages(where=«src»),

    python_requires=«>=3.6»,

)

Вначале мы импортируем пакет setuptools. После этого открываем файл README.md и помещаем его содержимое в переменную longdescription.

Затем вызываем функцию setuptools.setup() и передаем ей целый ряд параметров. При создании собственного пакета замените значения следующих параметров:

  • Название пакета (name). Оно должно быть уникальным. Для того чтобы обеспечить уникальность названия, проще всего добавить к нему свой логин на сайте https://test.pypi.org/ в формате «название-пакета-логин».
  • Также вы можете заменить поля author и author_email.

Менять остальные поля, в принципе, не обязательно.

  1. Файл README.md

В файле README.md содержатся описание пакета, примеры и технические детали проекта. Расширение .md указывает на то, что этот файл сохранен в формате markdown и поддерживает форматирование текста.

В документации на сайте www.markdownguide.org⧉ вы найдете рекомендации по использованию языка markdown.

В нашем файле мы напишем следующий текст.

# Тестовый пакет

Файл README.md может содержать описание, примеры и технические детали пакета.

Формат .md (markdown) поддерживает форматирование текста. Например, **полужирный шрифт** или *курсив*. Более полный перечень можно найти по [ссылке](https://guides.github.com/features/mastering-markdown/)

  1. Файл LICENSE.txt

Остается создать файл с лицензией LICENSE.txt. Мы будем использовать лицензию открытого и свободного программного обеспечения MIT (Массачусетского технологического института).

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

MIT License

Copyright (c) 2022 Dmitry Makarov

Permission is hereby granted, free of charge, to any person obtaining a copy

of this software and associated documentation files (the «Software»), to deal

in the Software without restriction, including without limitation the rights

to use, copy, modify, merge, publish, distribute, sublicense, and/or sell

copies of the Software, and to permit persons to whom the Software is

furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all

copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED «AS IS», WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE

SOFTWARE.

Шаг 3. Создание дистрибутива

Скачаем инструменты, необходимые для создания дистрибутива.

Теперь из папки base/ введите команду
py -m build. После ее выполнения должно появиться уведомление об успешном создании дистрибутива пакета.

создание дистрибутива собственного тестового пакета на Питоне

Сам дистрибутив появится в папке base/.

дистрибутив тестового пакета

Шаг 4. Подгрузка дистрибутива

Скачаем инструмент для подгрузки twine.

И выполним подгрузку посредством следующей команды

py -m twine upload —repository testpypi dist/*

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

Должен появиться вот такой результат.

подгрузка собственного пакета на TestPyPI

Как вы видите, подгруженный пакет доступен по адресу: https://test.pypi.org/project/example-package-DmitryMakarov/0.0.1/⧉. Мы создали свой первый пакет.

Установка и использование пакета

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

страница тестового пакета на Test.PyPI.org

pip install -i https://test.pypi.org/simple/ example-package-DmitryMakarov==0.0.1

Результат исполнения этой команды вы видите ниже.

установка собственного тестового пакета через pip

Теперь мы можем пользоваться нашим пакетом. В интерактивном режиме (команду py) импортируем модуль example_module из пакета example_package и вызовем функцию square(), передав ей, например, число два.

from example_package import example_module

example_module.square(2)

использование пакета, установленного из TestPyPI

В целом создание «взрослого» пакета на PyPI следует похожей схеме.

Подведем итог

Сегодня мы расширили наше представление о том, как запускать код на Питоне. Если раньше мы использовали только Google Colab, то теперь можем создавать собственные программы, модули и пакеты.

Вопросы для закрепления

Вопрос. Чем программа (скрипт) отличается от модуля?

Посмотреть правильный ответ

Ответ: технически и то, и другое — файл с расширением .py, при этом скрипт не предполагает его использования в других программах, а модуль предназначен именно для этого.

Вопрос. Зачем добавлять Питон в переменнаую окружения PATH в ОС Windows?

Посмотреть правильный ответ

Ответ: мы добавляем Питон в переменную PATH ОС Windows, чтобы в командной строке мы могли исполнять Питон в интерактивном режиме.

Вопрос. Что такое переменная path модуля sys в Питоне?

Посмотреть правильный ответ

Ответ: в переменной path модуля sys указаны те папки компьютера, в которых интерпретатор Питона будет искать файл для его импорта. Эта переменная отличается от переменной PATH в операционной системе.


Ответы на вопросы

Вопрос. Что такое
if __name__ == ‘__main__’:?

Ответ. Перед тем как исполнить код (например, в командной строке), интерпретатор Питона объявляет несколько переменных. Одна из них называется
__name__. Этой переменной присваивается значение __main__. Создадим файл script.py со следующим кодом и запустим его в командной строке.

значение переменной __name__ при исполнении кода напрямую

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

При этом если импортировать файл module.py внутри script.py, то значение переменной
__name__ файла module.py изменится на его название (то есть слово module). Создадим файл module.py со следующим кодом.

Заменим код в файле script.py и исполним его.

import module

print(__name__)

значение переменной __name__ при исполнении импортированного файла

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

Как мы получили такой результат? Сначала был импортирован код из файла module.py, внутри которого переменной
__name__ было присвоено значение module. Именно это значение мы видим на первой строке вывода. Затем было выведено значение переменной
__name__ файла script.py (то есть __main__).

Теперь давайте изменим код файла module.py.

if __name__ == ‘__main__’:

    print(‘This runs as the main file’)

else:

    print(‘This runs as an imported file’)

(1) Запустим его как основной файл напрямую из командной строки.

if __name__ == '__main__'

(2) Теперь импортируем его в script.py (код файла module.py оставим без изменений).

if __name__ == '__main__' внутри импортированного файла

Как вы видите, в импортированном файле module.py переменная
__name__ не содержит значения __main__, поэтому исполнилось выражение после else. В script.py переменная
__name__ по-прежнему имеет значение __main__.

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

Изменим файл module.py.

def foo():

    print(‘This is an imported function’)

if __name__ == ‘__main__’:

    foo()

Теперь внутри script.py вначале просто импортируем файл module.py.

функция с условием if __name__ == '__main__'

При вызове модуля ничего не произошло. Так и должно быть. Для того чтобы вызвать функцию foo() нам нужно обратиться к ней напрямую. Изменим файл script.py и исполним его.

import module

module.foo()

вызов функции с условием if __name__ == '__main__'

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

Помимо всего прочего, на Python можно создавать десктопные приложения с графическим интерфейсом (Python GUI). Это можно сделать с помощью PyQt и Qt Designer, с которыми мы сегодня познакомимся.

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

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

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

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

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

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

$ brew install pyqt5

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

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

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

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

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

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

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

Дизайн

Основы

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

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

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

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

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

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

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

Макеты

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Пишем код

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

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

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

Для Python 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:

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

Для добавления элементов в 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 без страха потерять написанный код.

Последнее обновление: 05.10.2023

Установка

Для создания программ на Python нам потребуется интерпретатор. Для его установки перейдем на страницу
https://www.python.org/downloads/ и найдем ссылку на загрузку последней версии языка:

Установка Python

По нажатию на кнопку будет загружен соответствующей текущей ОС установщик Python. Следует учитывать, что Windows 7 и более ранние версии не поддерживаются.

На ОС Windows при запуске инсталлятора запускает окно мастера установки:

Установка дистрибутива Python 3.11 на Windows

Здесь мы можем задать путь, по которому будет устанавливаться интерпретатор. Оставим его по умолчанию, то есть
C:\Users\[имя_пользователя]\AppData\Local\Programs\Python\Python312\.

Кроме того, в самом низу отметим флажок «Add Python 3.12 to PATH», чтобы добавить путь к интерпретатору в переменные среды.

После этого мы можем проверить установку Python и его версию, запустив в командной строке/треминале команду

python --version

Запуск интерпретатора

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

Как было сказано в прошлой теме, программа интерпретатора, если при установке не был изменен адрес, по умолчанию
устанавливается на Linux по пути usr/local/bin/python311, а на Windows по пути
C:\Users\[имя_пользователя]\AppData\Local\Programs\Python\Python311\ и представляет файл под названием python.exe.

Интерпретатор Python

Запустим интерпретатор и введем в него следующую строку:

print("hello world")

И консоль выведет строку «hello world»:

Python 3.12.0 (tags/v3.12.0:0fb18b0, Oct  2 2023, 13:03:39) [MSC v.1935 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> print("hello world")
hello world
>>>

Для этой программы использовалась функция print(), которая выводит некоторую строку на консоль.

Создание файла программы

В реальности, как правило, программы определяются во внешних файлах-скриптах и затем передаются интерпретатору на выполнение. Поэтому создадим файл программы.
Для этого на диске C или где-нибудь в другом месте файловой системы определим для скриптов папку python. А в этой папке создадим новый текстовый файл, который
назовем hello.py. По умолчанию файлы с кодом на языке Python, как правило, имеют расширение py.

Создание скрипта на языке Python

Откроем этот файл в любом текстовом редакторе и добавим в него следующий код:

name = input("Введите имя: ")
print("Привет,", name)

Python в Visual Studio Code

Скрипт состоит из двух строк. Первая строка с помощью функции input() ожидает ввода пользователем своего имени. Введенное
имя затем попадает в переменную name.

Вторая строка с помощью функции print() выводит приветствие вместе с введенным именем.

Теперь запустим командную строку/терминал и с помощью команды cd перейдем к папке, где находится файл с исходным кодом hello.py (например, в моем случае это папка C:\python).

cd c:\python

Далее вначале введем полный путь к интерпретатору, а затем полный путь к файлу скрипта. К примеру, в моем случае в консоль надо будет вести:

C:\Users\eugen\AppData\Local\Programs\Python\Python312\python.exe hello.py

Но если при установке была указана опция «Add Python 3.12 to PATH», то есть путь к интерпретатору Python был добавлен в переменные среды, то вместо полного пути к интерпретатору можно просто написать python:

python hello.py

Либо даже можно сократить:

py hello.py

Варианты с обоими способами запуска:

Microsoft Windows [Version 10.0.22621.2361]
(c) Корпорация Майкрософт (Microsoft Corporation). Все права защищены.

C:\Users\eugen>cd c:\python

c:\python>C:\Users\eugen\AppData\Local\Programs\Python\Python312\python.exe hello.py
Введите имя: Eugene
Привет, Eugene

c:\python>python hello.py
Введите имя: Tom
Привет, Tom

c:\python>py hello.py
Введите имя: Bob
Привет, Bob

c:\python>

В итоге программа выведет приглашение к вводу имени, а затем приветствие.

  • Создать пользователя windows 10 в безопасном режиме через командную строку
  • Создать приложение онлайн для windows
  • Создать привод восстановления windows 10
  • Создать правило для порта windows 10
  • Создать загрузочный файл windows 10