ээдд. Прохоренок_Н_А__Дронов_В_А_Python_3_и_PyQt_5_Разработка_приложен. Николай Прохоренок Владимир Дронов
Скачать 7.92 Mb.
|
Для создания перечислений применяются два класса, определенные в модуле enum : Enum — базовый класс для создания классов-перечислений, чьи элементы могут хранить значения произвольного типа. Для примера определим класс-перечисление Versions , имеющий два элемента: V2_7 со значением "2.7" и V3_6 со значением "3.6" (листинг 15.5). Отметим, что элементы пере- числений представляют собой атрибуты объекта класса. Глава 15. Итераторы, контейнеры и перечисления 283 Листинг 15.5. Перечисление с элементами произвольного типа from enum import Enum class Versions(Enum): V2_7 = "2.7" V3_6 = "3.6" IntEnum — базовый класс для создания перечислений, способных хранить лишь цело- численные значения. Листинг 15.6 представляет код перечисления Colors с тремя элементами, хранящими целые числа. Листинг 15.6. Перечисление с целочисленными элементами from enum import IntEnum class Colors(IntEnum): Red = 1 Green = 2 Blue = 3 Имена элементов перечислений должны быть уникальны (что и неудивительно — ведь фактически это атрибуты объекта класса). Однако разные элементы все же могут хранить одинаковые значения (листинг 15.7). Листинг 15.7. Перечисление с элементами, хранящими одинаковые значения from enum import Enum class Versions(Enum): V2_7 = "2.7" V3_6 = "3.6" MostFresh = "3.6" Чтобы объявить, что наше перечисление может хранить лишь уникальные значения, мы можем использовать декоратор unique , также определенный в модуле enum (листинг 15.8). Листинг 15.8. Использование декоратора unique from enum import Enum, unique @unique class Versions(Enum): V2_7 = "2.7" V3_6 = "3.6" Если мы попытаемся определить в классе, для которого был указан декоратор unique , эле- менты с одинаковыми значениями, то получим сообщение об ошибке. Определив перечисление, можно использовать его элементы в вычислениях: >>> e = Versions.V3_6 >>> e 284 Часть I. Основы языка Python >>> e.value '3.6' >>> e == Versions.V2_7 False Отметим, что для этого нам не придется создавать экземпляр класса. Это сделает сам Python, неявно создав экземпляр с тем же именем, что мы дали классу (вся необходимая для этого функциональность определена в базовых классах перечислений Enum и IntEnum ). Все классы перечислений принадлежат типу EnumMeta из модуля enum : >>> type(Colors) >>> from enum import EnumMeta >>> type(Colors) == EnumMeta True Однако элементы перечислений уже являются экземплярами их классов: >>> type(Colors.Red) >>> type(Colors.Red) == Colors True Над элементами перечислений можно производить следующие операции: обращаться к ним по их именам, использовав знакомую нам запись с точкой: >>> Versions.V3_6 >>> e = Versions.V3_6 >>> e обращаться к ним в стиле словарей, использовав в качестве ключа имя элемента: >>> Versions["V3_6"] обращаться к ним по их значениям, указав их в круглых скобках после имени класса перечисления: >>> Versions("3.6") получать имена соответствующих им атрибутов класса и их значения, воспользовавшись свойствами name и value соответственно: >>> Versions.V2_7.name, Versions.V2_7.value ('V2_7', '2.7') использовать в качестве итератора (необходимая для этого функциональность определе- на в базовых классах): >>> list(Colors) [ >>> for c in Colors: print(c.value, end = " ") 1 2 3 использовать в выражениях с применением операторов равенства, неравенства, in и not in : Глава 15. Итераторы, контейнеры и перечисления 285 >>> e = Versions.V3_6 >>> e == Versions.V3_6 True >>> e != Versions.V2_7 True >>> e in Versions True >>> e in Colors False Отметим, что элементы разных перечислений всегда не равны друг другу, даже если хранят одинаковые значения; использовать элементы перечислений — подклассов IntEnum в арифметических выраже- ниях и в качестве индексов перечислений. В этом случае они будут автоматически пре- образовываться в целые числа, соответствующие их значениям: >>> Colors.Red + 1 # Значение Colors.Red - 1 2 >>> Colors.Green != 3 # Значение Colors.Green - 2 True >>> ["a", "b", "c"][Colors.Red] 'b' Помимо элементов, классы перечислений могут включать атрибуты экземпляра класса и методы — как экземпляров, так и объектов класса. При этом методы экземпляра класса все- гда вызываются у элемента перечисления (и, соответственно, первым параметром ему пере- дается ссылка на экземпляр класса, представляющий элемент перечисления, у которого был вызван метод), а методы объекта класса — у самого класса перечисления. Для примера давайте рассмотрим код класса перечисления VersionExtended (листинг 15.9). Листинг 15.9. Перечисление, включающее атрибуты и методы from enum import Enum class VersionExtended(Enum): V2_7 = "2.7" V3_6 = "3.6" # Методы экземпляра класса. # Вызываются у элемента перечисления def describe(self): return self.name, self.value def __str__(self): return str(__class__.__name__) + "." + self.name + ": " + self.value # Метод объекта класса. # Вызывается у самого класса перечисления @classmethod def getmostfresh(cls): return cls.V3_6 286 Часть I. Основы языка Python В методе __str__() мы использовали встроенную переменную __class__ , хранящую ссылку на объект текущего класса. Атрибут __name__ этого объекта содержит имя класса в виде строки. Осталось лишь проверить готовый класс в действии, для чего мы введем следующий код: >>> d = VersionExtended.V2_7.describe() >>> print(d[0] + ", " + d[1]) V2_7, 2.7 >>> print(VersionExtended.V2_7) VersionExtended.V2_7: 2.7 >>> print(VersionExtended.getmostfresh()) VersionExtended.V3_6: 3.6 Осталось отметить одну важную деталь. На основе класса перечисления можно создавать подклассы только в том случае, если этот класс не содержит атрибутов объекта класса, т. е. собственно элементов перечисления. Если же класс перечисления содержит элементы, попытка определения его подкласса приведет к ошибке: >>> class ExtendedColors(Colors): pass Traceback (most recent call last): File " ", line 1, in NameError: name 'Colors' is not defined П РИМЕЧАНИЕ В составе стандартной библиотеки Python уже давно присутствует модуль struct, позво- ляющий создавать нечто похожее на перечисления. Однако он не столь удобен в работе, как инструменты, предлагаемые модулем enum. ГЛ А В А 16 Работа с файлами и каталогами Очень часто нужно сохранить какие-либо данные. Если эти данные имеют небольшой объем, их можно записать в файл. 16.1. Открытие файла Прежде чем работать с файлом, необходимо создать объект файла с помощью функции open() . Функция имеет следующий формат: open(<Путь к файлу>[, mode='r'][, buffering=-1][, encoding=None][, errors=None][, newline=None][, closefd=True]) В первом параметре указывается путь к файлу. Путь может быть абсолютным или относи- тельным. При указании абсолютного пути в Windows следует учитывать, что в Python слэш является специальным символом. По этой причине слэш необходимо удваивать или вместо обычных строк использовать неформатированные строки: >>> "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' Обратите внимание на последний пример. В этом пути из-за того, что слэши не удвоены, возникло присутствие сразу трех специальных символов: \t , \n и \f (отображается как \x0c ). После преобразования этих специальных символов путь будет выглядеть следующим образом: C:<Табуляция>emp<Перевод строки>ew<Перевод формата>ile.txt Если такую строку передать в функцию open() , это приведет к исключению OSError : >>> open("C:\temp\new\file.txt") Traceback (most recent call last): File " ", line 1, in OSError: [Errno 22] Invalid argument: 'C:\temp\new\x0cile.txt' Вместо абсолютного пути к файлу можно указать относительный путь, который определя- ется с учетом местоположения текущего рабочего каталога. Относительный путь будет авто- 288 Часть I. Основы языка Python матически преобразован в абсолютный путь с помощью функции abspath() из модуля os.path Возможны следующие варианты: если открываемый файл находится в текущем рабочем каталоге, можно указать только имя файла: >>> import os.path # Подключаем модуль >>> # Файл в текущем рабочем каталоге (C:\book\) >>> os.path.abspath(r"file.txt") 'C:\\book\\file.txt' если открываемый файл расположен во вложенной папке, перед именем файла через слэш указываются имена вложенных папок: >>> # Открываемый файл в C:\book\folder1\ >>> os.path.abspath(r"folder1/file.txt") 'C:\\book\\folder1\\file.txt' >>> # Открываемый файл в C:\book\folder1\folder2\ >>> os.path.abspath(r"folder1/folder2/file.txt") 'C:\\book\\folder1\\folder2\\file.txt' если папка с файлом расположена ниже уровнем, перед именем файла указываются две точки и слэш ( "../" ): >>> # Открываемый файл в C:\ >>> os.path.abspath(r"../file.txt") 'C:\\file.txt' если в начале пути расположен слэш, путь отсчитывается от корня диска. В этом случае местоположение текущего рабочего каталога не имеет значения: >>> # Открываемый файл в C:\book\folder1\ >>> os.path.abspath(r"/book/folder1/file.txt") 'C:\\book\\folder1\\file.txt' >>> # Открываемый файл в C:\book\folder1\folder2\ >>> os.path.abspath(r"/book/folder1/folder2/file.txt") 'C:\\book\\folder1\\folder2\\file.txt' Как можно видеть, в абсолютном и относительном путях можно указать как прямые, так и обратные слэши. Все они будут автоматически преобразованы с учетом значения атрибута sep из модуля os.path . Значение этого атрибута зависит от используемой операционной системы. Выведем значение атрибута sep в операционной системе Windows: >>> os.path.sep '\\' >>> os.path.abspath(r"C:/book/folder1/file.txt") 'C:\\book\\folder1\\file.txt' При использовании относительного пути необходимо учитывать местоположение текущего рабочего каталога, т. к. рабочий каталог не всегда совпадает с каталогом, в котором нахо- дится исполняемый файл. Если файл запускается с помощью двойного щелчка на его знач- ке, то каталоги будут совпадать. Если же файл запускается из командной строки, то теку- щим рабочим каталогом будет каталог, из которого запускается файл. Рассмотрим все это на примере, для чего в каталоге C:\book создадим следующую структуру файлов: Глава 16. Работа с файлами и каталогами 289 C:\book\ test.py folder1\ __init__.py module1.py Содержимое файла C:\book\test.py приведено в листинге 16.1. Листинг 16.1. Содержимое файла C:\book\test.py # -*- coding: utf-8 -*- import os, sys print("%-25s%s" % ("Файл:", os.path.abspath(__file__))) print("%-25s%s" % ("Текущий рабочий каталог:", os.getcwd())) print("%-25s%s" % ("Каталог для импорта:", sys.path[0])) print("%-25s%s" % ("Путь к файлу:", os.path.abspath("file.txt"))) print("-" * 40) import folder1.module1 as m m.get_cwd() Файл C:\book\folder1\__init__.py создаем пустым. Как вы уже знаете, этот файл указывает интерпретатору Python, что данный каталог является пакетом с модулями. Содержимое файла C:\book\folder1\module1.py приведено в листинге 16.2. Листинг 16.2. Содержимое файла C:\book\folder1\module1.py # -*- coding: utf-8 -*- import os, sys def get_cwd(): print("%-25s%s" % ("Файл:", os.path.abspath(__file__))) print("%-25s%s" % ("Текущий рабочий каталог:", os.getcwd())) print("%-25s%s" % ("Каталог для импорта:", sys.path[0])) print("%-25s%s" % ("Путь к файлу:", os.path.abspath("file.txt"))) Запускаем командную строку, переходим в каталог C:\book и запускаем файл test.py : C:\>cd C:\book C:\book>test.py Файл: C:\book\test.py Текущий рабочий каталог: C:\book Каталог для импорта: C:\book Путь к файлу: C:\book\file.txt ---------------------------------------- Файл: C:\book\folder1\module1.py Текущий рабочий каталог: C:\book Каталог для импорта: C:\book Путь к файлу: C:\book\file.txt В этом примере текущий рабочий каталог совпадает с каталогом, в котором расположен файл test.py . Однако обратите внимание на текущий рабочий каталог внутри модуля module1.py . Если внутри этого модуля в функции open() указать имя файла без пути, поиск файла будет произведен в каталоге C:\book , а не C:\book\folder1 290 Часть I. Основы языка Python Теперь перейдем в корень диска C: и опять запустим файл test.py : C:\book>cd C:\ C:\>C:\book\test.py Файл: C:\book\test.py Текущий рабочий каталог: C:\ Каталог для импорта: C:\book Путь к файлу: C:\file.txt ---------------------------------------- Файл: C:\book\folder1\module1.py Текущий рабочий каталог: C:\ Каталог для импорта: C:\book Путь к файлу: C:\file.txt В этом случае текущий рабочий каталог не совпадает с каталогом, в котором расположен файл test.py . Если внутри файлов test.py и module1.py в функции open() указать имя файла без пути, поиск файла будет производиться в корне диска C: , а не в каталогах с этими фай- лами. Чтобы поиск файла всегда производился в каталоге с исполняемым файлом, необходимо этот каталог сделать текущим с помощью функции chdir() из модуля os . Для примера соз- дадим файл test2.py (листинг 16.3). Листинг 16.3. Пример использования функции chdir() # -*- coding: utf-8 -*- import os, sys # Делаем каталог с исполняемым файлом текущим os.chdir(os.path.dirname(os.path.abspath(__file__))) print("%-25s%s" % ("Файл:", __file__)) print("%-25s%s" % ("Текущий рабочий каталог:", os.getcwd())) print("%-25s%s" % ("Каталог для импорта:", sys.path[0])) print("%-25s%s" % ("Путь к файлу:", os.path.abspath("file.txt"))) Обратите внимание на четвертую строку. С помощью атрибута __file__ мы получаем путь к исполняемому файлу вместе с именем файла. Атрибут __file__ не всегда содержит пол- ный путь к файлу. Например, если запуск осуществляется следующим образом: C:\book>C:\Python36\python test2.py то атрибут будет содержать только имя файла без пути. Чтобы всегда получать полный путь к файлу, следует передать значение атрибута в функцию abspath() из модуля os.path . Далее мы извлекаем путь (без имени файла) с помощью функции dirname() и передаем его функ- ции chdir() . Теперь, если в функции open() указать название файла без пути, поиск будет производиться в каталоге с этим файлом. Запустим файл test2.py с помощью командной строки: C:\>C:\book\test2.py Файл: C:\book\test2.py Текущий рабочий каталог: C:\book Каталог для импорта: C:\book Путь к файлу: C:\book\file.txt Глава 16. Работа с файлами и каталогами 291 Функции, предназначенные для работы с каталогами, мы еще рассмотрим подробно в сле- дующих разделах. Сейчас же важно запомнить, что текущим рабочим каталогом будет каталог, из которого запускается файл, а не каталог, в котором расположен исполняе- мый файл. Кроме того, пути поиска файлов не имеют никакого отношения к путям поиска модулей. Необязательный параметр mode в функции open() может принимать следующие значения: r — только чтение (значение по умолчанию). После открытия файла указатель устанав- ливается на начало файла. Если файл не существует, возбуждается исключение FileNotFoundError ; r+ — чтение и запись. После открытия файла указатель устанавливается на начало фай- ла. Если файл не существует, то возбуждается исключение FileNotFoundError ; w — запись. Если файл не существует, он будет создан. Если файл существует, он будет перезаписан. После открытия файла указатель устанавливается на начало файла; w+ — чтение и запись. Если файл не существует, он будет создан. Если файл существует, он будет перезаписан. После открытия файла указатель устанавливается на начало файла; a — запись. Если файл не существует, он будет создан. Запись осуществляется в конец файла. Содержимое файла не удаляется; a+ — чтение и запись. Если файл не существует, он будет создан. Запись осуществляется в конец файла. Содержимое файла не удаляется; x — создание файла для записи. Если файл уже существует, возбуждается исключение FileExistsError ; x+ — создание файла для чтения и записи. Если файл уже существует, возбуждается ис- ключение FileExistsError После указания режима может следовать модификатор: b — файл будет открыт в бинарном режиме. Файловые методы принимают и возвраща- ют объекты типа bytes ; t — файл будет открыт в текстовом режиме (значение по умолчанию в Windows). Фай- ловые методы принимают и возвращают объекты типа str . В этом режиме будет автома- тически выполняться обработка символа конца строки — так, в Windows при чтении вместо символов \r\n будет подставлен символ \n . Для примера создадим файл file.txt и запишем в него две строки: >>> f = open(r"file.txt", "w") # Открываем файл на запись >>> f.write("String1\nString2") # Записываем две строки в файл 15 >>> f.close() # Закрываем файл Поскольку мы указали режим w , то, если файл не существует, он будет создан, а если существует, то будет перезаписан. Теперь выведем содержимое файла в бинарном и текстовом режимах: >>> # Бинарный режим (символ \r остается) >>> with open(r"file.txt", "rb") as f: for line in f: print(repr(line)) b'String1\r\n' b'String2' |