ээдд. Прохоренок_Н_А__Дронов_В_А_Python_3_и_PyQt_5_Разработка_приложен. Николай Прохоренок Владимир Дронов
Скачать 7.92 Mb.
|
move(<Путь к файлу>, <Куда перемещаем>) — перемещает файл в указанное место с уда- лением исходного файла. Если файл существует, он будет перезаписан. Если файл не удалось переместить, возбуждается исключение OSError или одно из исключений, являющихся подклассом этого класса. В качестве результата возвращает путь переме- щенного файла. Пример перемещения файла file4.txt в каталог C:\book\test : >>> shutil.move(r"file4.txt", r"C:\book\test") Для переименования и удаления файлов предназначены следующие функции из модуля os : rename(<Старое имя>, <Новое имя>) — переименовывает файл. Если файл не удалось переименовать, возбуждается исключение OSError или одно из исключений, являющих- ся подклассом этого класса. Пример переименования файла с обработкой исключений: import os # Подключаем модуль try: os.rename(r"file3.txt", "file4.txt") except OSError: print("Файл не удалось переименовать") else: print("Файл успешно переименован") remove(<Путь к файлу>) и unlink(<Путь к файлу>) — позволяют удалить файл. Если файл не удалось удалить, возбуждается исключение OSError или одно из исключений, являющихся подклассом этого класса: >>> os.remove(r"file2.txt") >>> os.unlink(r"file4.txt") Модуль os.path содержит дополнительные функции, позволяющие проверить наличие файла, получить размер файла и др. Опишем эти функции: exists(<Путь или дескриптор>) — проверяет указанный путь на существование. В ка- честве параметра можно передать путь к файлу или целочисленный дескриптор откры- того файла, возвращенный функцией open() из того же модуля os . Возвращает True , если путь существует, и False — в противном случае: >>> import os.path >>> os.path.exists(r"file.txt"), os.path.exists(r"file2.txt") (True, False) >>> os.path.exists(r"C:\book"), os.path.exists(r"C:\book2") (True, False) getsize(<Путь к файлу>) — возвращает размер файла в байтах. Если файл не существу- ет, возбуждается исключение OSError : >>> os.path.getsize(r"file.txt") # Файл существует 18 Глава 16. Работа с файлами и каталогами 309 >>> os.path.getsize(r"file2.txt") # Файл не существует ... Фрагмент опущен ... OSError: [Error 2] Не удается найти указанный файл: 'file2.txt' getatime(<Путь к файлу>) — возвращает время последнего доступа к файлу в виде ко- личества секунд, прошедших с начала эпохи. Если файл не существует, возбуждается исключение OSError : >>> import time # Подключаем модуль time >>> t = os.path.getatime(r"file.txt") >>> t 1511773416.0529847 >>> time.strftime("%d.%m.%Y %H:%M:%S", time.localtime(t)) '27.11.2017 12:03:36' getctime(<Путь к файлу>) — возвращает дату создания файла в виде количества секунд, прошедших с начала эпохи. Если файл не существует, возбуждается исключение OSError : >>> t = os.path.getctime(r"file.txt") >>> t 1511773416.0529847 >>> time.strftime("%d.%m.%Y %H:%M:%S", time.localtime(t)) '27.11.2017 12:03:36' getmtime(<Путь к файлу>) — возвращает время последнего изменения файла в виде ко- личества секунд, прошедших с начала эпохи. Если файл не существует, возбуждается исключение OSError : >>> t = os.path.getmtime(r"file.txt") >>> t 1511773609.980973 >>> time.strftime("%d.%m.%Y %H:%M:%S", time.localtime(t)) '27.11.2017 12:06:49' Получить размер файла и время создания, изменения и доступа к файлу, а также значения других метаданных, позволяет функция stat() из модуля os . В качестве значения функция возвращает объект stat_result , содержащий десять атрибутов: st_mode , st_ino , st_dev , st_nlink , st_uid , st_gid , st_size , st_atime , st_mtime и st_ctime Пример использования функции stat() : >>> import os, time >>> s = os.stat(r"file.txt") >>> s os.stat_result(st_mode=33206, st_ino=5910974511035376, st_dev=2086732993, st_nlink=1, st_uid=0, st_gid=0, st_size=15, st_atime=1511773416, st_mtime=1511773609, st_ctime=1511773416) >>> s.st_size # Размер файла 15 >>> t = s.st_atime # Время последнего доступа к файлу >>> time.strftime("%d.%m.%Y %H:%M:%S", time.localtime(t)) '27.11.2017 12:03:36' >>> t = s.st_ctime # Время создания файла >>> time.strftime("%d.%m.%Y %H:%M:%S", time.localtime(t)) '27.11.2017 12:03:36' 310 Часть I. Основы языка Python >>> t = s.st_mtime # Время последнего изменения файла >>> time.strftime("%d.%m.%Y %H:%M:%S", time.localtime(t)) '27.11.2017 12:06:49' Обновить время последнего доступа и время изменения файла позволяет функция utime() из модуля os . Функция имеет два варианта формата: utime(<Путь к файлу или его дескриптор>, None) utime(<Путь к файлу или его дескриптор >, (<Последний доступ>, <Изменение файла>)) В качестве первого параметра можно указать как строковый путь, так и целочисленный дескриптор открытого файла, возвращенный функцией open() из модуля os . Если в качестве второго параметра указано значение None , то время доступа и изменения файла будет теку- щим. Во втором варианте формата функции utime() указывается кортеж из новых значений в виде количества секунд, прошедших с начала эпохи. Если файл не существует, возбужда- ется исключение OSError Пример использования функции utime() : >>> import os, time >>> os.stat(r"file.txt") # Первоначальные значения os.stat_result(st_mode=33206, st_ino=5910974511035376, st_dev=2086732993, st_nlink=1, st_uid=0, st_gid=0, st_size=15, st_atime=1511773416, st_mtime=1511773609, st_ctime=1511773416) >>> t = time.time() - 600 >>> os.utime(r"file.txt", (t, t)) # Текущее время минус 600 сек >>> os.stat(r"file.txt") os.stat_result(st_mode=33206, st_ino=5910974511035376, st_dev=2086732993, st_nlink=1, st_uid=0, st_gid=0, st_size=15, st_atime=1511790710, st_mtime=1511790710, st_ctime=1511773416) >>> os.utime(r"file.txt", None) # Текущее время >>> os.stat(r"file.txt") os.stat_result(st_mode=33206, st_ino=5910974511035376, st_dev=2086732993, st_nlink=1, st_uid=0, st_gid=0, st_size=15, st_atime=1511791343, st_mtime=1511791343, st_ctime=1511773416) 16.7. Преобразование пути к файлу или каталогу Преобразовать путь к файлу или каталогу позволяют следующие функции из модуля os.path : abspath(<Относительный путь>) — преобразует относительный путь в абсолютный, учи- тывая местоположение текущего рабочего каталога: >>> import os.path >>> os.path.abspath(r"file.txt") 'C:\\book\\file.txt' >>> os.path.abspath(r"folder1/file.txt") 'C:\\book\\folder1\\file.txt' >>> os.path.abspath(r"../file.txt") 'C:\\file.txt' Как уже отмечалось ранее, в относительном пути можно указать как прямые, так и об- ратные слэши. Все они будут автоматически преобразованы с учетом значения атрибута Глава 16. Работа с файлами и каталогами 311 sep из модуля os.path . Значение этого атрибута зависит от используемой операционной системы. Выведем значение атрибута sep в операционной системе Windows: >>> os.path.sep '\\' При указании пути в Windows следует учитывать, что слэш является специальным сим- волом. По этой причине слэш необходимо удваивать (экранировать) или вместо обыч- ных строк использовать неформатированные строки: >>> "C:\\temp\\new\\file.txt" # Правильно 'C:\\temp\\new\\file.txt' >>> r"C:\temp\new\file.txt" # Правильно 'C:\\temp\\new\\file.txt' >>> "C:\temp\new\file.txt" # Неправильно!!! 'C:\temp\new\x0cile.txt' Кроме того, если слэш расположен в конце строки, то его необходимо удваивать даже при использовании неформатированных строк: >>> r"C:\temp\new\" # Неправильно!!! SyntaxError: EOL while scanning string literal >>> r"C:\temp\new\\" 'C:\\temp\\new\\\\' В первом случае последний слэш экранирует закрывающую кавычку, что приводит к синтаксической ошибке. Решить эту проблему можно, удвоив последний слэш. Однако посмотрите на результат — два слэша превратились в четыре. От одной проблемы ушли, а к другой пришли. Поэтому в данном случае лучше использовать обычные строки: >>> "C:\\temp\\new\\" # Правильно 'C:\\temp\\new\\' >>> r"C:\temp\new\\"[:-1] # Можно и удалить слэш 'C:\\temp\\new\\' isabs(<Путь>) — возвращает True , если путь является абсолютным, и False — в против- ном случае: >>> os.path.isabs(r"C:\book\file.txt") True >>> os.path.isabs("file.txt") False basename(<Путь>) — возвращает имя файла без пути к нему: >>> os.path.basename(r"C:\book\folder1\file.txt") 'file.txt' >>> os.path.basename(r"C:\book\folder") 'folder' >>> os.path.basename("C:\\book\\folder\\") '' dirname(<Путь>) — возвращает путь к папке, где хранится файл: >>> os.path.dirname(r"C:\book\folder\file.txt") 'C:\\book\\folder' >>> os.path.dirname(r"C:\book\folder") 'C:\\book' 312 Часть I. Основы языка Python >>> os.path.dirname("C:\\book\\folder\\") 'C:\\book\\folder' split(<Путь>) — возвращает кортеж из двух элементов: пути к папке, где хранится файл, и имени файла: >>> os.path.split(r"C:\book\folder\file.txt") ('C:\\book\\folder', 'file.txt') >>> os.path.split(r"C:\book\folder") ('C:\\book', 'folder') >>> os.path.split("C:\\book\\folder\\") ('C:\\book\\folder', '') splitdrive(<Путь>) — разделяет путь на имя диска и остальную часть пути. В качестве значения возвращается кортеж из двух элементов: >>> os.path.splitdrive(r"C:\book\folder\file.txt") ('C:', '\\book\\folder\\file.txt') splitext(<Путь>) — возвращает кортеж из двух элементов: пути с именем файла, но без расширения, и расширения файла (фрагмент после последней точки): >>> os.path.splitext(r"C:\book\folder\file.tar.gz") ('C:\\book\\folder\\file.tar', '.gz') join(<Путь1>[, ..., <ПутьN>]) — соединяет указанные элементы пути, при необходи- мости вставляя между ними разделители: >>> os.path.join("C:\\", "book\\folder", "file.txt") 'C:\\book\\folder\\file.txt' >>> os.path.join(r"C:\\", "book/folder/", "file.txt") 'C:\\\\book/folder/file.txt' Обратите внимание на последний пример — в пути используются разные слэши, и в ре- зультате получен некорректный путь. Чтобы этот путь сделать корректным, необходимо воспользоваться функцией normpath() из того же модуля os.path : >>> p = os.path.join(r"C:\\", "book/folder/", "file.txt") >>> os.path.normpath(p) 'C:\\book\\folder\\file.txt' 16.8. Перенаправление ввода/вывода При рассмотрении методов для работы с файлами говорилось, что значение, возвращаемое методом fileno() , всегда будет больше числа 2, т. к. число 0 закреплено за стандартным вводом stdin , 1 — за стандартным выводом stdout , а 2 — за стандартным выводом сооб- щений об ошибках stderr . Все эти потоки имеют некоторое сходство с файловыми объек- тами. Например, потоки stdout и stderr поддерживают метод write() , предназначенный для вывода сообщений, а поток stdin — метод readline() , служащий для получения вво- димых пользователем данных. Если этим потокам присвоить ссылку на объект, поддержи- вающий файловые методы, то можно перенаправить стандартные потоки в соответствую- щий файл. Для примера так и сделаем: >>> import sys # Подключаем модуль sys >>> tmp_out = sys.stdout # Сохраняем ссылку на sys.stdout Глава 16. Работа с файлами и каталогами 313 >>> f = open(r"file.txt", "a") # Открываем файл на дозапись >>> sys.stdout = f # Перенаправляем вывод в файл >>> print("Пишем строку в файл") >>> sys.stdout = tmp_out # Восстанавливаем стандартный вывод >>> print("Пишем строку в стандартный вывод") Пишем строку в стандартный вывод >>> f.close() # Закрываем файл В этом примере мы вначале сохранили ссылку на стандартный вывод в переменной tmp_out С помощью этой переменной можно в дальнейшем восстановить вывод в стандартный поток. Функция print() напрямую поддерживает перенаправление вывода. Для этого использует- ся параметр file , который по умолчанию ссылается на стандартный поток вывода. Напри- мер, записать строку в файл можно так: >>> f = open(r"file.txt", "a") >>> print("Пишем строку в файл", file=f) >>> f.close() Параметр flush позволяет указать, когда следует выполнять непосредственное сохранение данных из промежуточного буфера в файле. Если его значение равно False (это, кстати, значение по умолчанию), сохранение будет выполнено лишь после закрытия файла или по- сле вызова метода flush() . Чтобы указать интерпретатору Python выполнять сохранение после каждого вызова функции print() , следует присвоить этому параметру значение True : >>> f = open(r"file.txt", "a") >>> print("Пишем строку в файл", file = f, flush = True) >>> print("Пишем другую строку в файл", file = f, flush = True) >>> f.close() Стандартный ввод stdin также можно перенаправить. В этом случае функция input() будет читать одну строку из файла при каждом вызове. При достижении конца файла возбуждает- ся исключение EOFError . Для примера выведем содержимое файла с помощью перенаправ- ления потока ввода (листинг 16.4). Листинг 16.4. Перенаправление потока ввода # -*- coding: utf-8 -*- import sys tmp_in = sys.stdin # Сохраняем ссылку на sys.stdin f = open(r"file.txt", "r") # Открываем файл на чтение sys.stdin = f # Перенаправляем ввод while True: try: line = input() # Считываем строку из файла print(line) # Выводим строку except EOFError: # Если достигнут конец файла, break # выходим из цикла sys.stdin = tmp_in # Восстанавливаем стандартный ввод f.close() # Закрываем файл input() 314 Часть I. Основы языка Python Если необходимо узнать, ссылается ли стандартный ввод на терминал или нет, можно вос- пользоваться методом isatty() . Метод возвращает True , если объект ссылается на терми- нал, и False — в противном случае: >>> tmp_in = sys.stdin # Сохраняем ссылку на sys.stdin >>> f = open(r"file.txt", "r") >>> sys.stdin = f # Перенаправляем ввод >>> sys.stdin.isatty() # Не ссылается на терминал False >>> sys.stdin = tmp_in # Восстанавливаем стандартный ввод >>> sys.stdin.isatty() # Ссылается на терминал True >>> f.close() # Закрываем файл Перенаправить стандартный ввод/вывод можно также с помощью командной строки. Для примера создадим в папке C:\book файл test3.py с кодом, приведенным в листинге 16.5. Листинг 16.5. Содержимое файла test3.py # -*- coding: utf-8 -*- while True: try: line = input() print(line) except EOFError: break Запускаем командную строку и переходим в папку со скриптом, выполнив команду: cd C:\book . Теперь выведем содержимое созданного ранее текстового файла file.txt (его содер- жимое может быть любым), выполнив команду: C:\Python36\python.exe test3.py < file.txt Перенаправить стандартный вывод в файл можно аналогичным образом. Только в этом случае символ < необходимо заменить символом > . Для примера создадим в папке C:\book файл test4.py с кодом из листинга 16.6. Листинг 16.6. Содержимое файла test4.py # -*- coding: utf-8 -*- print("String") # Эта строка будет записана в файл Теперь перенаправим вывод в файл file.txt , выполнив команду: C:\Python36\python.exe test4.py > file.txt В этом режиме файл file.txt будет перезаписан. Если необходимо добавить результат в конец файла, следует использовать символы >> . Вот пример дозаписи в файл: C:\Python36\python.exe test4.py >> file.txt С помощью стандартного вывода stdout можно создать индикатор выполнения процесса непосредственно в окне консоли. Чтобы реализовать такой индикатор, нужно вспомнить, что Глава 16. Работа с файлами и каталогами 315 символ перевода строки в Windows состоит из двух символов: \r (перевод каретки) и \n (перевод строки). Таким образом, используя только символ перевода каретки \r , можно перемещаться в начало строки и перезаписывать ранее выведенную информацию. Рассмот- рим вывод индикатора процесса на примере (листинг 16.7). Листинг 16.7. Индикатор выполнения процесса # -*- coding: utf-8 -*- import sys, time for i in range(5, 101, 5): sys.stdout.write("\r ... %s%%" % i) # Обновляем индикатор sys.stdout.flush() # Сбрасываем содержимое буфера time.sleep(1) # Засыпаем на 1 секунду sys.stdout.write("\rПроцесс завершен\n") input() Сохраним код в файл и запустим его с помощью двойного щелчка. В окне консоли записи будут заменять друг друга на одной строке каждую секунду. Так как данные перед выводом будут помещаться в буфер, мы сбрасываем их на диск явным образом с помощью метода flush() 16.9. Сохранение объектов в файл Сохранить объекты в файл и в дальнейшем восстановить объекты из файла позволяют модули pickle и shelve . Модуль pickle предоставляет следующие функции: dump(<Объект>, <Файл>[, <Протокол>][, fix_imports=True]) — производит сериализа- цию объекта и записывает данные в указанный файл. В параметре <Файл> указывается файловый объект, открытый на запись в бинарном режиме. Пример сохранения объекта в файл: >>> import pickle >>> f = open(r"file.txt", "wb") >>> obj = ["Строка", (2, 3)] >>> pickle.dump(obj, f) >>> f.close() load() — читает данные из файла и преобразует их в объект. Формат функции: load(<Файл>[, fix_imports=True][, encoding="ASCII"] [, errors="strict"]) В параметре <Файл> указывается файловый объект, открытый на чтение в бинарном ре- жиме. Пример восстановления объекта из файла: >>> f = open(r"file.txt", "rb") >>> obj = pickle.load(f) >>> obj ['Строка', (2, 3)] >>> f.close() 316 Часть I. Основы языка Python В один файл можно сохранить сразу несколько объектов, последовательно вызывая функ- цию dump() . Вот пример сохранения нескольких объектов: >>> obj1 = ["Строка", (2, 3)] >>> obj2 = (1, 2) >>> f = open(r"file.txt", "wb") >>> pickle.dump(obj1, f) # Сохраняем первый объект >>> pickle.dump(obj2, f) # Сохраняем второй объект >>> f.close() Для восстановления объектов необходимо несколько раз вызвать функцию load() : >>> f = open(r"file.txt", "rb") >>> obj1 = pickle.load(f) # Восстанавливаем первый объект >>> obj2 = pickle.load(f) # Восстанавливаем второй объект >>> obj1, obj2 (['Строка', (2, 3)], (1, 2)) >>> f.close() Сохранить объект в файл можно также с помощью метода dump(<Объект>) класса Pickler Конструктор класса имеет следующий формат: Pickler(<Файл>[, <Протокол>][, fix_imports=True]) Пример сохранения объекта в файл: >>> f = open(r"file.txt", "wb") >>> obj = ["Строка", (2, 3)] >>> pkl = pickle.Pickler(f) >>> pkl.dump(obj) >>> f.close() Восстановить объект из файла позволяет метод load() из класса Unpickler . Формат конст- руктора класса: Unpickler(<Файл>[, fix_imports=True][, encoding="ASCII"] [, errors="strict"]) Пример восстановления объекта из файла: >>> f = open(r"file.txt", "rb") >>> obj = pickle.Unpickler(f).load() >>> obj ['Строка', (2, 3)] >>> f.close() Модуль pickle позволяет также преобразовать объект в последовательность байтов и вос- становить объект из таковой. Для этого предназначены две функции: dumps(<Объект>[, <Протокол>][, fix_imports=True]) — производит сериализацию объ- екта и возвращает последовательность байтов специального формата. Формат зависит от указанного протокола — числа от 0 до значения pickle.HIGHEST_PROTOCOL в порядке от более старых к более новым и совершенным. По умолчанию в качестве номера про- токола используется значение: pickle.DEFAULT_PROTOCOL ( 3 ). Пример преобразования списка и кортежа: >>> obj1 = [1, 2, 3, 4, 5] # Список >>> obj2 = (6, 7, 8, 9, 10) # Кортеж Глава 16. Работа с файлами и каталогами 317 >>> pickle.dumps(obj1) b'\x80\x03]q\x00(K\x01K\x02K\x03K\x04K\x05e.' >>> pickle.dumps(obj2) b'\x80\x03(K\x06K\x07K\x08K\tK\ntq\x00.' loads(<Последовательность байтов>[, fix_imports=True][, encoding="ASCII"][, errors="strict"]) — преобразует последовательность байтов специального формата в объект. Пример восстановления списка и кортежа: >>> pickle.loads(b'\x80\x03]q\x00(K\x01K\x02K\x03K\x04K\x05e.') [1, 2, 3, 4, 5] >>> pickle.loads(b'\x80\x03(K\x06K\x07K\x08K\tK\ntq\x00.') (6, 7, 8, 9, 10) Модуль shelve позволяет сохранять объекты под заданным строковым ключом и предос- тавляет интерфейс доступа, сходный со словарями, позволяя тем самым создать нечто, по- добное базе данных. Для сериализации объекта используются возможности модуля pickle , а для записи получившейся строки по ключу в файл — модуль dbm . Все эти действия модуль shelve производит самостоятельно. Открыть файл с набором объектов поможет функция open() . Функция имеет следующий формат: open(<Путь к файлу>[, flag="c"][, protocol=None][, writeback=False]) В необязательном параметре flag можно указать один из режимов открытия файла: r — только чтение; w — чтение и запись; c — чтение и запись (значение по умолчанию). Если файл не существует, он будет соз- дан; n — чтение и запись. Если файл не существует, он будет создан. Если файл существует, он будет перезаписан. Функция open() возвращает объект, с помощью которого производится дальнейшая работа с базой данных. Этот объект имеет следующие методы: close() — закрывает файл с базой данных. Для примера создадим файл и сохраним в нем список и кортеж: >>> import shelve # Подключаем модуль >>> db = shelve.open("db1") # Открываем файл >>> db["obj1"] = [1, 2, 3, 4, 5] # Сохраняем список >>> db["obj2"] = (6, 7, 8, 9, 10) # Сохраняем кортеж >>> db["obj1"], db["obj2"] # Вывод значений ([1, 2, 3, 4, 5], (6, 7, 8, 9, 10)) >>> db.close() # Закрываем файл keys() — возвращает объект с ключами; values() — возвращает объект со значениями; items() — возвращает объект-итератор, который на каждой итерации генерирует кор- теж, содержащий ключ и значение: 318 Часть I. Основы языка Python >>> db = shelve.open("db1") >>> db.keys(), db.values() (KeysView( ValuesView( >>> list(db.keys()), list(db.values()) (['obj1', 'obj2'], [[1, 2, 3, 4, 5], (6, 7, 8, 9, 10)]) >>> db.items() ItemsView( >>> list(db.items()) [('obj1', [1, 2, 3, 4, 5]), ('obj2', (6, 7, 8, 9, 10))] >>> db.close() get(<Ключ>[, <Значение по умолчанию>]) — если ключ присутствует, метод возвращает значение, соответствующее этому ключу. Если ключ отсутствует, возвращается значе- ние None или значение, указанное во втором параметре; setdefault(<Ключ>[, <Значение по умолчанию>]) — если ключ присутствует, метод воз- вращает значение, соответствующее этому ключу. Если ключ отсутствует, создается новый элемент со значением, указанным во втором параметре, и в качестве результата возвращается это значение. Если второй параметр не указан, значением нового элемента будет None ; pop(<Ключ>[, <Значение по умолчанию>]) — удаляет элемент с указанным ключом и возвращает его значение. Если ключ отсутствует, возвращается значение из второго параметра. Если ключ отсутствует, и второй параметр не указан, возбуждается исключе- ние KeyError ; popitem() — удаляет произвольный элемент и возвращает кортеж из ключа и значения. Если файл пустой, возбуждается исключение KeyError ; clear() — удаляет все элементы. Метод ничего не возвращает в качестве значения; update() — добавляет элементы. Метод изменяет текущий объект и ничего не возвра- щает. Если элемент с указанным ключом уже присутствует, то его значение будет пере- записано. Форматы метода: update(<Ключ1>=<Значение1>[, ..., <КлючN>=<ЗначениеN>]) update(<Словарь>) update(<Список кортежей с двумя элементами>) update(<Список списков с двумя элементами>) Помимо этих методов можно воспользоваться функцией len() для получения количества элементов и оператором del для удаления определенного элемента, а также операторами in и not in для проверки существования или несуществования ключа: >>> db = shelve.open("db1") >>> len(db) # Количество элементов 2 >>> "obj1" in db True >>> del db["obj1"] # Удаление элемента >>> "obj1" in db False >>> "obj1" not in db True >>> db.close() Глава 16. Работа с файлами и каталогами 319 16.10. Функции для работы с каталогами Для работы с каталогами используются следующие функции из модуля os : getcwd() — возвращает текущий рабочий каталог. От значения, возвращаемого этой функцией, зависит преобразование относительного пути в абсолютный. Кроме того, важно помнить, что текущим рабочим каталогом будет каталог, из которого запускается файл, а не каталог с исполняемым файлом: >>> import os >>> os.getcwd() # Текущий рабочий каталог 'C:\\book' chdir(<Имя каталога>) — делает указанный каталог текущим: >>> os.chdir("C:\\book\\folder1\\") >>> os.getcwd() # Текущий рабочий каталог 'C:\\book\\folder1' mkdir(<Имя каталога>[, <Права доступа>]) — создает новый каталог с правами досту- па, указанными во втором параметре. Права доступа задаются восьмеричным числом (значение по умолчанию 0o777 ). Пример создания нового каталога в текущем рабочем каталоге: >>> os.mkdir("newfolder") # Создание каталога rmdir(<Имя каталога>) — удаляет пустой каталог. Если в каталоге есть файлы или ука- занный каталог не существует, возбуждается исключение — подкласс класса OSError Удалим каталог newfolder : >>> os.rmdir("newfolder") # Удаление каталога listdir(<Путь>) — возвращает список объектов в указанном каталоге: >>> os.listdir("C:\\book\\folder1\\") ['file1.txt', 'file2.txt', 'file3.txt', 'folder1', 'folder2'] walk() — позволяет обойти дерево каталогов. Формат функции: walk(<Начальный каталог>[, topdown=True][, onerror=None] [, followlinks=False]) В качестве значения функция walk() возвращает объект. На каждой итерации через этот объект доступен кортеж из трех элементов: текущего каталога, списка каталогов и спи- ска файлов, находящихся в нем. Если произвести изменения в списке каталогов во время выполнения, это позволит изменить порядок обхода вложенных каталогов. Необязательный параметр topdown задает последовательность обхода каталогов. Если в качестве значения указано True (значение по умолчанию), последовательность обхода будет такой: >>> for (p, d, f) in os.walk("C:\\book\\folder1\\"): print(p) C:\book\folder1\ C:\book\folder1\folder1_1 C:\book\folder1\folder1_1\folder1_1_1 C:\book\folder1\folder1_1\folder1_1_2 C:\book\folder1\folder1_2 320 Часть I. Основы языка Python Если в параметре topdown указано значение False , последовательность обхода будет другой: >>> for (p, d, f) in os.walk("C:\\book\\folder1\\", False): print(p) C:\book\folder1\folder1_1\folder1_1_1 C:\book\folder1\folder1_1\folder1_1_2 C:\book\folder1\folder1_1 C:\book\folder1\folder1_2 C:\book\folder1\ Благодаря такой последовательности обхода каталогов можно удалить все вложенные файлы и каталоги. Это особенно важно при удалении каталога, т. к. функция rmdir() по- зволяет удалить только пустой каталог. Пример очистки дерева каталогов: import os for (p, d, f) in os.walk("C:\\book\\folder1\\", False): for file_name in f: # Удаляем все файлы os.remove(os.path.join(p, file_name)) for dir_name in d: # Удаляем все каталоги os.rmdir(os.path.join(p, dir_name)) В НИМАНИЕ ! Очень осторожно используйте этот код. Если в качестве первого параметра в функции walk() указать корневой каталог диска, то все имеющиеся в нем файлы и каталоги будут удалены. Удалить дерево каталогов позволяет также функция rmtree() из модуля shutil . Функ- ция имеет следующий формат: rmtree(<Путь>[, <Обработка ошибок>[, <Обработчик ошибок>]]) Если в параметре <Обработка ошибок> указано значение True , ошибки будут проигнори- рованы. Если указано значение False (значение по умолчанию), в третьем параметре можно задать ссылку на функцию, которая будет вызываться при возникновении исклю- чения. Пример удаления дерева каталогов вместе с начальным каталогом: import shutil shutil.rmtree("C:\\book\\folder1\\") normcase(<Каталог>) — преобразует заданный к каталогу путь к виду, подходящему для использования в текущей операционной системе. В Windows преобразует все прямые слэши в обратные. Также во всех системах приводит все буквы пути к нижнему ре- гистру: >>> from os.path import normcase >>> normcase(r"c:/BoOk/fIlE.TxT") 'c:\\book\\file.txt' Как вы уже знаете, функция listdir() возвращает список объектов в указанном каталоге. Проверить, на какой тип объекта ссылается элемент этого списка, можно с помощью сле- дующих функций из модуля os.path : Глава 16. Работа с файлами и каталогами 321 isdir(<Объект>) — возвращает True , если объект является каталогом, и False — в про- тивном случае: >>> import os.path >>> os.path.isdir(r"C:\book\file.txt") False >>> os.path.isdir("C:\\book\\") True isfile(<Объект>) — возвращает True , если объект является файлом, и False — в про- тивном случае: >>> os.path.isfile(r"C:\book\file.txt") True >>> os.path.isfile("C:\\book\\") False islink(<Объект>) — возвращает True , если объект является символической ссылкой, и False — в противном случае. Если символические ссылки не поддерживаются, функция возвращает False Функция listdir() возвращает список всех объектов в указанном каталоге. Если необхо- димо ограничить список определенными критериями, следует воспользоваться функцией glob(<Путь>) из модуля glob . Функция glob() позволяет указать в пути следующие специ- альные символы: ? — любой одиночный символ; * — любое количество символов; [<Символы>] — позволяет указать символы, которые должны быть на этом месте в пути. Можно задать символы или определить их диапазон через дефис. В качестве значения функция возвращает список путей к объектам, совпадающим с шабло- ном. Вот пример использования функции glob() : >>> import os, glob >>> os.listdir("C:\\book\\folder1\\") ['file.txt', 'file1.txt', 'file2.txt', 'folder1_1', 'folder1_2', 'index.html'] >>> glob.glob("C:\\book\\folder1\\*.txt") ['C:\\book\\folder1\\file.txt', 'C:\\book\\folder1\\file1.txt', 'C:\\book\\folder1\\file2.txt'] >>> glob.glob("C:\\book\\folder1\\*.html") # Абсолютный путь ['C:\\book\\folder1\\index.html'] >>> glob.glob("folder1/*.html") # Относительный путь ['folder1\\index.html'] >>> glob.glob("C:\\book\\folder1\\*[0-9].txt") ['C:\\book\\folder1\\file1.txt', 'C:\\book\\folder1\\file2.txt'] >>> glob.glob("C:\\book\\folder1\\*\\*.html") ['C:\\book\\folder1\\folder1_1\\index.html', 'C:\\book\\folder1\\folder1_2\\test.html'] Обратите внимание на последний пример. Специальные символы могут быть указаны не только в названии файла, но и в именах каталогов в пути. Это позволяет просматривать сра- зу несколько каталогов в поисках объектов, соответствующих шаблону. 322 Часть I. Основы языка Python 16.10.1. Функция scandir() Начиная с Python 3.5, в модуле os появилась поддержка функции scandir() — более быст- рого и развитого инструмента для просмотра содержимого каталогов. Формат функции: os.scandir(<Путь>) <Путь> можно указать как относительный, так и абсолютный. Если он не задан, будет ис- пользовано строковое значение (точка), т. е. путь к текущему каталогу. Функция scandir() возвращает итератор, на каждом проходе возвращающий очередной элемент — файл или каталог, что присутствует по указанному пути. Этот файл или каталог представляется экземпляром класса DirEntry , определенного в том же модуле os , который хранит всевозможные сведения о файле (каталоге). Класс DirEntry поддерживает атрибуты: name — возвращает имя файла (каталога); path — возвращает путь к файлу (каталогу), составленный из пути, что был указан в вы- зове функции scandir() , и имени файла (каталога), хранящегося в свойстве name Для примера выведем список путей всех файлов и каталогов, находящихся в текущем ката- логе (при вводе команд в Python Shell текущим станет каталог, где установлен Python): >>> import os >>> for entry in os.scandir(): print(entry.name) .\DLLs .\Doc .\include # Часть вывода пропущена .\python.exe .\python3.dll .\vcruntime140.dll Видно, что путь, возвращаемый свойством path , составляется из пути, заданного в вызове функции scandir() (в нашем случае это используемый по умолчанию путь ), и имени фай- ла (каталога). Теперь попробуем указать путь явно: >>> for entry in os.scandir("c:\python36"): print(entry.path) c:\python36\DLLs c:\python36\Doc c:\python36\include # Часть вывода пропущена c:\python36\python.exe c:\python36\python3.dll c:\python36\vcruntime140.dll Помимо описанных ранее атрибутов, класс DirEntry поддерживает следующие методы: is_file(follow_symlinks=True) — возвращает True , если текущий элемент — файл, и False в противном случае. Если элемент представляет собой символическую ссылку, и для параметра follow_symlinks указано значение True (или если параметр вообще Глава 16. Работа с файлами и каталогами 323 опущен), проверяется элемент, на который указывает эта символическая ссылка. Если же для параметра follow_symlinks задано значение False , всегда возвращается False ; is_dir(follow_symlinks=True) — возвращает True , если текущий элемент — каталог, и False в противном случае. Если элемент представляет собой символическую ссылку, и для параметра follow_symlinks указано значение True (или если параметр вообще опущен), проверяется элемент, на который указывает эта символическая ссылка. Если же для параметра follow_symlinks задано значение False , всегда возвращается False ; is_symlink() — возвращает True , если текущий элемент — символическая ссылка, и False в противном случае; stat(follow_symlinks=True) — возвращает объект stat_result , хранящий сведения о файле (более подробно он был описан в разд. 16.6). Если элемент представляет собой символическую ссылку, и для параметра follow_symlinks указано значение True (или если параметр вообще опущен), возвращаются сведения об элементе, на который указы- вает эта символическая ссылка. Если же для параметра follow_symlinks задано значение False , возвращаются сведения о самой символической ссылке. В Windows атрибуты st_ino , st_dev и st_nlink объекта stat_result , возвращенного методом stat() , всегда хранят 0 , и для получения их значений следует воспользоваться функцией stat() из мо- дуля os , описанной в разд. 16.6. Рассмотрим пару примеров: для начала выведем список всех каталогов, что находятся в каталоге, где установлен Python, разделив их запятыми: >>> for entry in os.scandir(): if entry.is_dir(): print(entry.name, end=", ") DLLs, Doc, include, Lib, libs, Scripts, tcl, Tools, выведем список всех DLL-файлов, хранящихся в каталоге Windows, без обработки сим- волических ссылок: >>> for entry in os.scandir("c:\windows"): if entry.is_file(follow_symlinks=False) and entry.name.endswith(".dll"): print(entry.name, end=", ") В Python 3.6 итератор, возвращаемый функцией scandir() , получил поддержку протокола менеджеров контекста (см. разд. 16.2). Так что мы можем выполнить просмотр содержимо- го какого-либо пути следующим способом: >>> with os.scandir() as it: for entry in it: print(entry.name) В том же Python 3.6 для Windows появилась возможность указывать путь в вызове функции scandir() в виде объекта bytes . Однако нужно иметь в виду, что в таком случае значения атрибутов name и path класса DirEntry также будут представлять собой объекты bytes , а не строки: >>> with os.scandir(b"c:\python36") as it: for entry in it: print(entry.name) 324 Часть I. Основы языка Python b'DLLs' b'Doc' b'include' # Часть вывода пропущена b'python.exe' b'python3.dll' b'vcruntime140.dll' 16.11. Исключения, возбуждаемые файловыми операциями В этой главе неоднократно говорилось, что функции и методы, осуществляющие файловые операции, при возникновении нештатных ситуаций возбуждают исключение класса OSError или одно из исключений, являющихся его подклассами. Настало время познакомиться с ними. Исключений-подклассов класса OSError довольно много. Вот те из них, что затрагивают именно операции с файлами и каталогами: BlockingIOError — не удалось заблокировать объект (файл или поток ввода/вывода); ConnectionError — ошибка сетевого соединения. Может возникнуть при открытии фай- ла по сети. Является базовым классом для ряда других исключений более высокого уровня, описанных в документации по Python; FileExistsError — файл или каталог с заданным именем уже существуют; FileNotFoundError — файл или каталог с заданным именем не обнаружены; InterruptedError — файловая операция неожиданно прервана по какой-либо причине; IsADirectoryError — вместо пути к файлу указан путь к каталогу; NotADirectoryError — вместо пути к каталогу указан путь к файлу; PermissionError — отсутствуют права на доступ к указанному файлу или каталогу; TimeoutError — истекло время, отведенное системой на выполнение операции. Вот пример кода, обрабатывающего некоторые из указанных исключений: try open("C:\temp\new\file.txt") except FileNotFoundError: print("Файл отсутствует") except IsADirectoryError: print("Это не файл, а каталог") except PermissionError: print("Отсутствуют права на доступ к файлу") except OSError: print("Неустановленная ошибка открытия файла") ЧАСТЬ II Библиотека PyQt 5 Глава 17. Знакомство с PyQt 5 Глава 18. Управление окном приложения Глава 19. Обработка сигналов и событий Глава 20. Размещение компонентов в окнах Глава 21. Основные компоненты Глава 22. Списки и таблицы Глава 23. Работа с базами данных Глава 24. Работа с графикой Глава 25. Графическая сцена Глава 26. Диалоговые окна Глава 27. Создание SDI- и MDI-приложений Глава 28. Мультимедиа Глава 29. Печать документов Глава 30. Взаимодействие с Windows Глава 31. Сохранение настроек приложений Глава 32. Приложение «Судоку» ГЛ А В А 17 Знакомство с PyQt 5 Итак, изучение основ языка Python закончено, и мы можем перейти к рассмотрению биб- лиотеки PyQt, позволяющей разрабатывать приложения с графическим интерфейсом. Пер- вые три главы второй части книги можно считать основными, поскольку в них описывают- ся базовые возможности библиотеки и методы, которые наследуют все компоненты, так что материал этих глав нужно знать обязательно. Остальные главы содержат дополнительный справочный материал. В сопровождающий книгу электронный архив (см. приложение) включен файл PyQt.doc , который содержит более 750 дополнительных листингов, пояс- няющих материал второй части книги, — что позволило уменьшить ее объем, поскольку с этими листингами страниц книги было бы вдвое больше, чем всех страниц ее второй части. 17.1. Установка PyQt 5 Библиотека PyQt 5 не входит в комплект поставки Python, и прежде чем начать изучение ее основ, необходимо установить эту библиотеку на компьютер. В настоящее время установка библиотеки PyQt 5 выполняется исключительно просто. Для этого достаточно запустить командную строку и отдать в ней команду: pip3 install PyQt5 Утилита pip3, поставляемая в составе Python и предназначенная для установки дополни- тельных библиотек, самостоятельно загрузит последнюю версию PyQt 5 и установит ее по пути <каталог, в котором установлен Python>\lib\site-packages\PyQt5 В НИМАНИЕ ! При установке PyQt таким способом устанавливаются только компоненты библиотеки, не- обходимые для запуска программ. Средства разработчика (такие как программа Designer) и дополнительные компоненты, в частности клиентские части серверных СУБД, должны быть установлены отдельно. Чтобы проверить правильность установки, выведем версии PyQt и Qt: >>> from PyQt5 import QtCore >>> QtCore.PYQT_VERSION_STR '5.9.2' >>> QtCore.QT_VERSION_STR '5.9.3' 328 Часть II. Библиотека PyQt 5 17.2. Первая программа При изучении языков и технологий принято начинать с программы, выводящей надпись «Привет, мир!». Не станем нарушать традицию и напишем программу (листинг 17.1), соз- дающую окно с приветствием и кнопкой для закрытия окна (рис. 17.1). Рис. 17.1. Результат выполнения листинга 17.1 Листинг 17.1. Первая программа на PyQt # -*- coding: utf-8 -*- from PyQt5 import QtWidgets import sys app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() window.setWindowTitle("Первая программа на PyQt") window.resize(300, 70) label = QtWidgets.QLabel(" Для создания файла с программой можно по-прежнему пользоваться редактором IDLE. Однако запуск оконного приложения из IDLE нажатием клавиши До сих пор мы создавали файлы с расширением py и все результаты выполнения программы выводили в консоль. Оконное приложение также можно сохранить с расширением py , но тогда при его запуске, помимо основного окна, также будет выведено окно консоли, что, впрочем, на этапе разработки дает возможность вывести отладочную информацию (таким способом мы будем пользоваться в дальнейших примерах). Чтобы избавиться от окна кон- соли, следует сохранять файл с расширением pyw Попробуйте создать два файла с различными расширениями и запустить двойным щелчком каждый из них. Глава 17. Знакомство с PyQt 5 329 17.3. Структура PyQt-программы Запускать программу мы научились. Теперь рассмотрим код из листинга 17.1 построчно. В первой строке указывается кодировка файла. Поскольку в Python 3 по умолчанию для сохранения исходного кода используется кодировка UTF-8, эту строку можно и не указы- вать. Во второй строке импортируется модуль QtWidgets — он содержит классы, реали- зующие компоненты графического интерфейса: окна, надписи, кнопки, текстовые поля и др. В третьей строке импортируется модуль sys , из которого нам потребуется список па- раметров, переданных в командной строке ( argv ), а также функция exit() , позволяющая завершить выполнение программы. Выражение: app = QtWidgets.QApplication(sys.argv) создает объект приложения в виде экземпляра класса QApplication . Конструктор этого класса принимает список параметров, переданных в командной строке. Следует помнить, что в программе всегда должен быть объект приложения, причем обязательно только один. Может показаться, что после создания объекта он в программе больше нигде не использу- ется, однако надо понимать, что с его помощью осуществляется управление приложением незаметно для нас. Получить доступ к этому объекту из любого места в программе можно через атрибут qApp из модуля QtWidgets . Например, вывести список параметров, передан- ных в командной строке, можно так: print(QtWidgets.qApp.argv()) Следующее выражение: window = QtWidgets.QWidget() создает объект окна в виде экземпляра класса QWidget . Этот класс наследуют практически все классы, реализующие компоненты графического интерфейса. И любой компонент, не имеющий родителя, обладает своим собственным окном. Выражение: window.setWindowTitle("Первая программа на PyQt") задает текст, который будет выводиться в заголовке окна, для чего используется метод setWindowTitle() Очередное выражение: window.resize(300, 70) задает минимальные размеры окна. В первом параметре метода resize() указывается ши- рина окна, а во втором параметре — его высота. При этом надо учитывать, что метод resize() устанавливает размеры не самого окна, а его клиентской области, при этом разме- ры заголовка и ширина границ окна не учитываются. Также следует помнить, что эти раз- меры являются рекомендацией, — т. е., если компоненты не помещаются в окне, оно будет увеличено. Выражение: label = QtWidgets.QLabel(" QLabel . Обратите внимание, что внутри строки мы указали HTML-теги, — а именно: с по- мощью тега произвели выравнивание текста по центру компонента. Возможность 330 Часть II. Библиотека PyQt 5 использования HTML-тегов и CSS-атрибутов является отличительной чертой библиотеки PyQt — например, внутри надписи можно вывести таблицу или отобразить изображение. Это очень удобно. Следующее выражение: btnQuit = QtWidgets.QPushButton("&Закрыть окно") создает объект кнопки. Текст, который будет отображен на кнопке, задается в качестве па- раметра в конструкторе класса QPushButton . Обратите внимание на символ & перед буквой З — таким образом задаются клавиши быстрого доступа. Если нажать одновременно кла- вишу & , то кнопка срабо- тает. Выражение: vbox = QtWidgets.QVBoxLayout() создает вертикальный контейнер. Все компоненты, добавляемые в этот контейнер, будут располагаться по вертикали сверху вниз в порядке добавления, при этом размеры добавлен- ных компонентов будут подогнаны под размеры контейнера. При изменении размеров кон- тейнера будет произведено изменение размеров всех компонентов. В следующих двух выражениях: vbox.addWidget(label) vbox.addWidget(btnQuit) с помощью метода addWidget() производится добавление созданных ранее объектов надпи- си и кнопки в вертикальный контейнер. Так как объект надписи добавляется первым, он будет расположен над кнопкой. При добавлении компонентов в контейнер они автомати- чески становятся потомками контейнера. Новое выражение: window.setLayout(vbox) добавляет контейнер в основное окно с помощью метода setLayout() . Таким образом, кон- тейнер становится потомком основного окна. Выражение: btnQuit.clicked.connect(app.quit) назначает обработчик сигнала clicked() кнопки, который генерируется при ее нажатии. Этот сигнал доступен через одноименный атрибут класса кнопки и поддерживает метод connect() , который и назначает для него обработчик, передаваемый первым параметром. Обработчик представляет собой метод quit() объекта приложения, выполняющий немед- ленное завершение его работы. Такой метод принято называть слотом. П ОЯСНЕНИЕ Сигналом в PyQt называется особое уведомление, генерируемое при наступлении какого- либо события в приложении: нажатия кнопки, ввода символа в текстовое поле, закрытия окна и пр. Очередное выражение: window.show() выводит на экран окно и все компоненты, которые мы ранее в него добавили. Глава 17. Знакомство с PyQt 5 331 И, наконец, последнее выражение: sys.exit(app.exec_()) запускает бесконечный цикл обработки событий в приложении. Код, расположенный после вызова метода exec_() , будет выполнен только после заверше- ния работы приложения, — поскольку результат выполнения метода exec_() мы передаем функции exit() , дальнейшее выполнение программы будет прекращено, а код возврата передан операционной системе. 17.4. ООП-стиль создания окна Библиотека PyQt написана в объектно-ориентированном стиле (ООП-стиле) и содержит несколько сотен классов. Иерархия наследования всех классов имеет слишком большой размер, и приводить ее в книге возможности нет. Тем не менее, чтобы показать зависимо- сти, при описании компонентов иерархия наследования конкретного класса будет показы- ваться. В качестве примера выведем базовые классы класса QWidget : >>> from PyQt5 import QtWidgets >>> QtWidgets.QWidget.__bases__ ( Как видно из примера, класс QWidget наследует два класса: QObject и QPaintDevice . Класс QObject является классом верхнего уровня, и его в PyQt наследуют большинство классов. В свою очередь, класс QWidget является базовым классом для всех визуальных компонентов. В НИМАНИЕ ! В описании каждого класса PyQt приводятся лишь атрибуты, методы, сигналы и слоты, оп- ределенные непосредственно в описываемом классе. Атрибуты, методы, сигналы и слоты базовых классов там не описываются — присутствуют лишь ссылки на соответствующие страницы документации. В своих программах вы можете наследовать стандартные классы и добавлять новую функ- циональность. В качестве примера переделаем соответствующим образом код из листин- га 17.1 и создадим окно в ООП-стиле (листинг 17.2). Листинг 17.2. ООП-стиль создания окна # -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) self.label = QtWidgets.QLabel("Привет, мир!") self.label.setAlignment(QtCore.Qt.AlignHCenter) self.btnQuit = QtWidgets.QPushButton("&Закрыть окно") self.vbox = QtWidgets.QVBoxLayout() self.vbox.addWidget(self.label) self.vbox.addWidget(self.btnQuit) self.setLayout(self.vbox) self.btnQuit.clicked.connect(QtWidgets.qApp.quit) 332 Часть II. Библиотека PyQt 5 if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() # Создаем экземпляр класса window.setWindowTitle("ООП-стиль создания окна") window.resize(300, 70) window.show() # Отображаем окно sys.exit(app.exec_()) # Запускаем цикл обработки событий В первых двух строках кода, как обычно, указывается кодировка файла и импортируются необходимые модули. На этот раз, помимо уже знакомого модуля QtWidgets , нам понадо- бится модуль QtCore , в котором объявлены атрибуты, задающие, в том числе, и режим вы- равнивания текста в объекте надписи. Далее мы определяем класс MyWindow , который наследует класс QWidget : class MyWindow(QtWidgets.QWidget): Можно наследовать и другие классы, являющиеся наследниками QWidget , — например, QFrame (окно с рамкой) или QDialog (диалоговое окно). При наследовании класса QDialog окно будет выравниваться по центру экрана (или по центру родительского окна) и иметь в заголовке окна только две кнопки: Справка и Закрыть. Кроме того, можно наследовать класс QMainWindow , который представляет главное окно приложения с меню, панелями инст- рументов и строкой состояния. Наследование класса QMainWindow имеет свои отличия, кото- рые мы рассмотрим в главе 27. Выражение: def __init__(self, parent=None): определяет конструктор класса. В качестве параметров он принимает ссылки на экземпляр класса ( self ) и на родительский компонент ( parent ). Родительский компонент может отсут- ствовать, поэтому в определении конструктора параметру присваивается значение по умол- чанию ( None ). Внутри метода __init__() вызывается конструктор базового класса, и ему передается ссылка на родительский компонент: QtWidgets.QWidget.__init__(self, parent) Следующие выражения внутри конструктора создают объекты надписи, кнопки и контей- нера, затем добавляют компоненты в контейнер, а сам контейнер — в основное окно. Сле- дует обратить внимание на то, что объекты надписи и кнопки сохраняются в атрибутах экземпляра класса. В дальнейшем из методов класса можно управлять этими объектами — например, изменять текст надписи. Если объекты не сохранить, то получить к ним доступ будет не так просто. В предыдущем примере (см. листинг 17.1) мы выравнивали надпись с помощью HTML- тегов. Однако выравнивание можно задать и вызовом метода setAlignment() , которому следует передать атрибут AlignHCenter из модуля QtCore : self.label.setAlignment(QtCore.Qt.AlignHCenter) В выражении, назначающем обработчик сигнала: self.btnQuit.clicked.connect(QtWidgets.qApp.quit) мы получаем доступ к объекту приложения через рассмотренный ранее атрибут qApp моду- ля QtWidgets Глава 17. Знакомство с PyQt 5 333 Создание объекта приложения и экземпляра класса MyWindow производится внутри условия: if __name__ == "__main__": Если вы внимательно читали первую часть книги, то уже знаете, что атрибут модуля __name__ будет содержать значение __main__ только в случае запуска модуля как главной программы. Если модуль импортировать, этот атрибут будет содержать другое значение. Поэтому весь последующий код создания объекта приложения и объекта окна выполняется только при запуске программы двойным щелчком на значке файла. Может возникнуть во- прос, зачем это нужно? Дело в том, что одним из преимуществ ООП-стиля программирова- ния является повторное использование кода. Следовательно, можно импортировать модуль и использовать класс MyWindow в другом приложении. Рассмотрим эту возможность на примере, для чего сохраним код из листинга 17.2 в файле с именем MyWindow.py , а затем создадим в той же папке еще один файл (например, с име- нем test.pyw ) и вставим в него код из листинга 17.3. Листинг 17.3. Повторное использование кода при ООП-стиле # -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets import MyWindow class MyDialog(QtWidgets.QDialog): def __init__(self, parent=None): QtWidgets.QDialog.__init__(self, parent) self.myWidget = MyWindow.MyWindow() self.myWidget.vbox.setContentsMargins(0, 0, 0, 0) self.button = QtWidgets.QPushButton("&Изменить надпись") mainBox = QtWidgets.QVBoxLayout() mainBox.addWidget(self.myWidget) mainBox.addWidget(self.button) self.setLayout(mainBox) self.button.clicked.connect(self.on_clicked) def on_clicked(self): self.myWidget.label.setText("Новая надпись") self.button.setDisabled(True) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyDialog() window.setWindowTitle("Преимущество ООП-стиля") window.resize(300, 100) window.show() sys.exit(app.exec_()) Теперь запустим файл test.pyw двойным щелчком — на экране откроется окно с надписью и двумя кнопками (рис. 17.2). По нажатию на кнопку Изменить надпись производится изме- 334 Часть II. Библиотека PyQt 5 Рис. 17.2. Результат выполнения листинга 17.3 нение текста надписи, и кнопка делается неактивной. Нажатие кнопки Закрыть окно будет по-прежнему завершать выполнение приложения. В этом примере мы создали класс MyDialog , который наследует класс QDialog . Поэтому при выводе окно автоматически выравнивается по центру экрана, а в заголовке окна выводятся только две кнопки: Справка и Закрыть. Внутри конструктора мы создаем экземпляр клас- са MyWindow и сохраняем его в атрибуте myWidget : self.myWidget = MyWindow.MyWindow() С его помощью позже мы получим доступ ко всем атрибутам класса MyWindow . Например, в следующей строке произведем изменение отступа между границами контейнера и грани- цами соседних элементов: self.myWidget.vbox.setContentsMargins(0, 0, 0, 0) В следующих инструкциях внутри конструктора создаются кнопки и контейнер, затем экземпляр класса MyWindow и кнопка добавляются в контейнер, а сам контейнер помещается в основное окно. Выражение: self.button.clicked.connect(self.on_clicked) назначает обработчик нажатия кнопки. В качестве параметра указывается ссылка на метод on_clicked() , внутри которого производится изменение текста надписи (с помощью метода setText() ), и кнопка делается неактивной (с помощью метода setDisabled() ). Внутри ме- тода on_clicked() доступен указатель self , через который можно получить доступ к атри- бутам классов MyDialog и MyWindow Вот так и производится повторное использование ранее написанного кода: мы создаем класс и сохраняем его внутри отдельного модуля, а чтобы протестировать модуль или ис- пользовать его как отдельное приложение, размещаем код создания объекта приложения и объекта окна внутри условия: if __name__ == "__main__": Тогда при запуске с помощью двойного щелчка на значке файла производится выполнение кода как отдельного приложения. Если модуль импортируется, то создание объекта прило- жения не производится, и мы можем использовать класс в других приложениях. Например, так, как это было сделано в листинге 17.3, или путем наследования класса и добавления или переопределения методов. В некоторых случаях использование ООП-стиля является обязательным. Например, чтобы обработать нажатие клавиши на клавиатуре, необходимо наследовать какой-либо класс и переопределить в нем метод с предопределенным названием. Какие методы необходимо переопределять, мы рассмотрим при изучении обработки событий. Глава 17. Знакомство с PyQt 5 335 17.5. Создание окна с помощью программы Qt Designer Если вы ранее пользовались Visual Studio или Delphi, то вспомните, как с помощью мыши размещали на форме компоненты: щелкали левой кнопкой мыши на нужной кнопке в пане- ли инструментов и перетаскивали компонент на форму, затем с помощью инспектора свойств производили настройку значений некоторых свойств, а остальные свойства получа- ли значения по умолчанию. При этом весь код генерировался автоматически. Произвести аналогичную операцию в PyQt позволяет программа Qt Designer, которая входит в состав этой библиотеки. К огромному сожалению, в составе последних версий PyQt эта полезная программа отсут- ствует. Однако ее можно установить отдельно в составе программного пакета PyQt 5 Tools, отдав в командной строке команду: pip3 install pyqt5-tools 17.5.1. Создание формы Запустить Qt Designer можно щелчком на исполняемом файле designer.exe , который рас- полагается по пути <путь, по которому установлен Python>\Lib\site-packages\pyqt5-tools К сожалению, через меню Пуск это сделать не получится. В окне New Form открывшегося окна (рис. 17.3) выбираем пункт Widget и нажимаем кноп- ку Create — откроется окно с пустой формой, на которую с помощью мыши можно пере- таскивать компоненты из панели Widget Box. Рис. 17.3. Программа Qt Designer 336 Часть II. Библиотека PyQt 5 В качестве примера добавим на форму надпись и кнопку. Для этого на панели Widget Box в группе Display Widgets щелкнем левой кнопкой мыши на пункте Label и, не отпуская кнопку мыши, перетащим компонент на форму. Затем проделаем аналогичную операцию с компонентом Push Button, находящимся в группе Buttons, и разместим его ниже надписи. Теперь выделим одновременно надпись и кнопку, щелкнем правой кнопкой мыши на лю- бом компоненте и в контекстном меню выберем пункт Lay out | Lay Out Vertically. Чтобы компоненты занимали всю область формы, щелкнем правой кнопкой мыши на свободном месте формы и в контекстном меню выберем пункт Lay out | Lay Out Horizontally. Теперь изменим некоторые свойства окна. Для этого в панели Object Inspector (рис. 17.4) выделим первый пункт (Form), перейдем в панель Property Editor, найдем свойство objectName и справа от свойства введем значение MyForm . Затем найдем свойство geometry, щелкнем мышью на значке уголка слева, чтобы отобразить скрытые свойства, и зададим ширину равной 300 , а высоту равной 70 (рис. 17.5), — размеры формы автоматически изме- нятся. Указать текст, который будет отображаться в заголовке окна, позволяет свойство windowTitle. Чтобы изменить свойства надписи, следует выделить компонент с помощью мыши или вы- брать соответствующий ему пункт в панели Object Inspector. Для примера изменим значе- ние свойства text (оно задает текст надписи). После чего найдем свойство alignment, щелк- нем мышью на значке уголка слева, чтобы отобразить скрытые свойства, и укажем для свойства Horizontal значение AlignHCenter . Теперь выделим кнопку и изменим значение свойства objectName на btnQuit , а в свойстве text укажем текст надписи, которая будет вы- водиться на кнопке. (Кстати, изменить текст надписи или кнопки также можно, выполнив двойной щелчок мышью на компоненте.) Рис. 17.4. Панель Object Inspector Рис. 17.5. Панель Property Editor Глава 17. Знакомство с PyQt 5 337 Закончив, выберем в меню File пункт Save и сохраним готовую форму в файл MyForm.ui При необходимости внести в этот файл какие-либо изменения, его можно открыть в про- грамме Qt Designer, выбрав в меню File пункт Open. 17.5.2. Использование UI-файла в программе Как вы можете убедиться, внутри UI-файла содержится текст в XML-формате, а не про- граммный код на языке Python. Следовательно, подключить файл с помощью инструкции import не получится. Чтобы использовать UI-файл внутри программы, следует воспользо- ваться модулем uic , который входит в состав библиотеки PyQt. Прежде чем использовать функции из этого модуля, необходимо подключить модуль с помощью инструкции: from PyQt5 import uic Для загрузки UI-файла предназначена функция loadUi() . Формат функции: loadUi( |