ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 05.12.2023
Просмотров: 827
Скачиваний: 3
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
IndentationError
— неправильно расставлены отступы в программе;
IndexError
— указанный индекс не существует в последовательности;
KeyError
— указанный ключ не существует в словаре;
KeyboardInterrupt
— нажата комбинация клавиш
MemoryError
— интерпретатору существенно не хватает оперативной памяти;
Глава 14. Обработка исключений
273
NameError
— попытка обращения к идентификатору до его определения;
NotImplementedError
— должно возбуждаться в абстрактных методах;
OSError
— базовый класс для всех исключений, возбуждаемых в ответ на возникновение ошибок в операционной системе (отсутствие запрошенного файла, недостаток места на диске и пр.);
OverflowError
— число, получившееся в результате выполнения арифметической опера- ции, слишком велико, чтобы Python смог его обработать;
RecursionError
— превышено максимальное количество проходов рекурсии;
RuntimeError
— неклассифицированная ошибка времени выполнения;
StopIteration
— возбуждается методом
__next__()
как сигнал об окончании итераций;
SyntaxError
— синтаксическая ошибка;
SystemError
— ошибка в самой программе интерпретатора Python;
TabError
— в исходном коде программы встретился символ табуляции, использование которого для создания отступов недопустимо;
TypeError
— тип объекта не соответствует ожидаемому;
UnboundLocalError
— внутри функции переменной присваивается значение после обра- щения к одноименной глобальной переменной;
UnicodeDecodeError
— ошибка преобразования последовательности байтов в строку;
UnicodeEncodeError
— ошибка преобразования строки в последовательность байтов;
UnicodeTranslationError
— ошибка преобразования строки в другую кодировку;
ValueError
— переданный параметр не соответствует ожидаемому значению;
ZeroDivisionError
— попытка деления на ноль.
14.4. Пользовательские исключения
Для возбуждения пользовательских исключений предназначены две инструкции: raise и
assert
Инструкция raise возбуждает заданное исключение. Она имеет несколько вариантов фор- мата: raise <Экземпляр класса> raise <Название класса> raise <Экземпляр или название класса> from <Объект исключения> raise
В первом варианте формата инструкции raise указывается экземпляр класса возбуждае- мого исключения. При создании экземпляра можно передать конструктору класса данные, которые станут доступны через второй параметр в инструкции except
. Приведем пример возбуждения встроенного исключения
ValueError
:
>>> raise ValueError("Описание исключения")
Traceback (most recent call last):
File "
", line 1, in
ValueError: Описание исключения
274
Часть I. Основы языка Python
Пример обработки этого исключения показан в листинге 14.10.
Листинг 14.10. Программное возбуждение исключения try: raise ValueError("Описание исключения") except ValueError as msg: print(msg) # Выведет: Описание исключения
В качестве исключения можно указать экземпляр пользовательского класса (листинг 14.11).
Листинг 14.11. Создание собственного исключения class MyError(Exception): def __init__(self, value): self.msg = value def __str__(self): return self.msg
# Обработка пользовательского исключения try: raise MyError("Описание исключения") except MyError as err: print(err) # Вызывается метод __str__() print(err.msg) # Обращение к атрибуту класса
# Повторно возбуждаем исключение raise MyError("Описание исключения")
Результат выполнения:
Описание исключения
Описание исключения
Traceback (most recent call last):
File "D:/Data/Документы/Работа/Книги/Python 3 и PyQt 5 Разработка приложений
II/Примеры/14/14.11.py", line 13, in
MyError: Описание исключения
Класс
Exception поддерживает все необходимые методы для вывода сообщения об ошибке.
Поэтому в большинстве случаев достаточно создать пустой класс, который наследует класс
Exception
(листинг 14.12).
Листинг 14.12. Упрощенный способ создания собственного исключения class MyError(Exception): pass try: raise MyError("Описание исключения") except MyError as err: print(err) # Выведет: Описание исключения
Во втором варианте формата инструкции raise в первом параметре задается объект клас- са, а не экземпляр:
Глава 14. Обработка исключений
275 try: raise ValueError # Эквивалентно: raise ValueError() except ValueError: print("Сообщение об ошибке")
В третьем варианте формата инструкции raise в первом параметре задается экземпляр класса или просто название класса, а во втором параметре указывается объект исключения.
В этом случае объект исключения сохраняется в атрибуте
__cause__
. При обработке вло- женных исключений эти данные используются для вывода информации не только о послед- нем исключении, но и о первоначальном исключении. Пример этого варианта формата ин- струкции raise можно увидеть в листинге 14.13.
Листинг 14.13. Применение третьего варианта формата инструкции raise try: x = 1 / 0 except Exception as err: raise ValueError() from err
Результат выполнения:
Traceback (most recent call last):
File "D:/Data/Документы/Работа/Книги/Python 3 и PyQt 5 Разработка приложений
II/Примеры/14/14.13.py", line 2, in
ZeroDivisionError: division by zero
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "D:/Data/Документы/Работа/Книги/Python 3 и PyQt 5 Разработка приложений
II/Примеры/14/14.13.py", line 4, in
ValueError
Как видно из результата, мы получили информацию не только по исключению
ValueError
, но и по исключению
ZeroDivisionError
. Следует заметить, что при отсутствии инструкции from информация сохраняется неявным образом. Если убрать инструкцию from в предыду- щем примере, мы получим следующий результат:
Traceback (most recent call last):
File "D:/Data/Документы/Работа/Книги/Python 3 и PyQt 5 Разработка приложений
II/Примеры/14/14.13.py", line 2, in
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "D:/Data/Документы/Работа/Книги/Python 3 и PyQt 5 Разработка приложений
II/Примеры/14/14.13.py", line 4, in
ValueError
276
Часть I. Основы языка Python
Четвертый вариант формата инструкции raise позволяет повторно возбудить последнее исключение и обычно применяется в коде, следующем за инструкцией except
. Пример это- го варианта показан в листинге 14.14.
Листинг 14.14. Применение четвертого варианта формата инструкции raise class MyError(Exception): pass try: raise MyError("Сообщение об ошибке") except MyError as err: print(err) raise # Повторно возбуждаем исключение
Результат выполнения:
Сообщение об ошибке
Traceback (most recent call last):
File "D:/Data/Документы/Работа/Книги/Python 3 и PyQt 5 Разработка приложений
II/Примеры/14/14.14.py", line 3, in
MyError: Сообщение об ошибке
Инструкция assert возбуждает исключение
AssertionError
, если логическое выражение возвращает значение
False
. Инструкция имеет следующий формат: assert <Логическое выражение>[, <Данные>]
Инструкция assert эквивалентна следующему коду: if __debug__: if not <Логическое выражение>: raise AssertionError(<Данные>)
Если при запуске программы используется флаг
-O
, то переменная
__debug__
будет иметь ложное значение. Таким образом можно удалить все инструкции assert из байт-кода.
Пример использования инструкции assert представлен в листинге 14.15.
Листинг 14.15. Использование инструкции assert try: x = -3 assert x >= 0, "Сообщение об ошибке" except AssertionError as err: print(err) # Выведет: Сообщение об ошибке
ГЛ А В А
15
Итераторы, контейнеры и перечисления
Язык Python поддерживает средства для создания классов особого назначения: итераторов, контейнеров и перечислений.
Итераторы — это классы, генерирующие последовательности каких-либо значений. Такие классы мы можем задействовать, например, в циклах for
: class MyIterator: # Определяем класс-итератор it = MyIterator() # Создаем его экземпляр for v in it: # и используем в цикле for
Контейнеры — классы, которые могут выступать как последовательности (списки или кор- тежи) или отображения (словари). Мы можем обратиться к любому элементу экземпляра такого класса через его индекс или ключ: class MyList: # Определяем класс-список class MyDict: # Определяем класс-словарь lst, dct = MyList(), MyDict() # Используем их lst[0] = 1 dct["first"] = 578 print(lst[1]), print(dct["second"])
Перечисления — особые классы, представляющие наборы каких-либо именованных вели- чин. В этом смысле они аналогичны подобным типам данных, доступным в других языках программирования, — например, в C: from enum import Enum # Импортируем базовый класс Enum class Versions(Enum): # Определяем класс-перечисление
Python2.7 = "2.7"
Python3.6 = "3.6"
# Используем его if python_version == Versions.Python3.6:
278
Часть I. Основы языка Python
15.1. Итераторы
Для того чтобы превратить класс в итератор, нам следует переопределить в нем два специ- альных метода:
__iter__(self)
— говорит о том, что этот класс является итератором (поддерживает итерационный протокол, как говорят Python-программисты). Он должен возвращать сам экземпляр этого класса, а также при необходимости может выполнять всевозможные предустановки.
Если в классе одновременно определены методы
__iter__()
и
__getitem__()
(о нем будет рассказано позже), предпочтение отдается первому методу;
__next__(self)
— вызывается при выполнении каждой итерации и должен возвращать очередное значение из последовательности. Если последовательность закончилась, в этом методе следует возбудить исключение
StopIteration
, которое сообщит вызы- вающему коду об окончании итераций.
Для примера рассмотрим класс, хранящий строку и на каждой итерации возвращающий очередной ее символ, начиная с конца (листинг 15.1).
Листинг 15.1. Класс-итератор class ReverseString: def __init__(self, s): self.__s = s def __iter__(self): self.__i = 0 return self def __next__(self): if self.__i > len(self.__s) - 1: raise StopIteration else: a = self.__s[-self.__i - 1] self.__i = self.__i + 1 return a
Проверим его в действии:
>>> s = ReverseString("Python")
>>> for a in s: print(a, end="") nohtyP
Результат вполне ожидаем — строка, выведенная задом наперед.
Также мы можем переопределить специальный метод
__len()__
, который вернет количест- во элементов в последовательности, и, разумеется, специальные методы
__str()__
и
__repr()__
, возвращающие строковое представление итератора (все эти методы были рас- смотрены в главе 13).
Перепишем код нашего класса-итератора, добавив в него определение методов
__len()__
и
__str()__
(листинг 15.2 — часть кода опущена).
Глава 15. Итераторы, контейнеры и перечисления
279
Листинг 15.2. Расширенный класс-итератор class ReverseString: def __len__(self): return len(self.__s) def __str__(self): return self.__s[::-1]
Теперь мы можем получить длину последовательности, хранящейся в экземпляре класса
ReverseString
, и его строковое представление:
>>> s = ReverseString("Python")
>>> print(len(s))
6
>>> print(str(s)) nohtyP
15.2. Контейнеры
Python позволяет создать как контейнеры-последовательности, аналогичные спискам и кор- тежам, так и контейнеры-отображения, т. е. словари. Сейчас мы узнаем, как это делается.
15.2.1. Контейнеры-последовательности
Чтобы класс смог реализовать функциональность последовательности, нам следует переоп- ределить в нем следующие специальные методы:
__getitem__(self, <Индекс>)
— вызывается при извлечении элемента последовательно- сти по его индексу с помощью операции
<Экземпляр класса>[<Индекс>]
. Метод должен возвращать значение, расположенное по этому индексу. Если индекс не является целым числом или срезом, должно возбуждаться исключение
TypeError
, а если индекса как та- кового не существует, следует возбудить исключение
IndexError
;
__setitem__(self, <Индекс>, <Значение>)
— вызывается в случае присваивания нового значения элементу последовательности с заданным индексом (операция
<Экземпляр класса>[<Индекс>] = <Новое значение>
). Метод не должен возвращать результата.
В случае задания индекса недопустимого типа и отсутствия такого индекса в последова- тельности следует возбуждать те же исключения, что и в случае метода
__getitem__()
;
__delitem__(self, <Ключ>)
— вызывается в случае удаления элемента последовательно- сти с заданным индексом с помощью выражения del <Экземпляр класса>[<Ключ>]
Метод не должен возвращать результата. В случае задания индекса недопустимого типа и отсутствия такого индекса в последовательности следует возбуждать те же исключе- ния, что и в случае метода
__getitem__()
;
__contains__(self, <Значение>)
— вызывается при проверке существования заданного значения в последовательности с применением операторов in и not in
. Метод должен возвращать
True
, если такое значение есть, и
False
— в противном случае.
В классе-последовательности мы можем дополнительно реализовать функциональность итератора (см. разд. 15.1), переопределив специальные методы
__iter__()
,
__next__()
и
__len__()
. Чаще всего так и поступают.
280
Часть I. Основы языка Python
Мы уже давно знаем, что строки в Python являются неизменяемыми. Давайте же напишем класс
MutableString
, представляющий строку, которую можно изменять теми же способа- ми, что и список (листинг 15.3).
Листинг 15.3. Класс MutableString class MutableString: def __init__(self, s): self.__s = list(s)
# Реализуем функциональность итератора def __iter__(self): self.__i = 0 return self def __next__(self): if self.__i > len(self.__s) - 1: raise StopIteration else: a = self.__s[self.__i] self.__i = self.__i + 1 return a def __len__(self): return len(self.__s) def __str__(self): return "".join(self.__s)
# Определяем вспомогательный метод, который будет проверять
# корректность индекса def __iscorrectindex(self, i): if type(i) == int or type(i) == slice: if type(i) == int and i > self.__len__() - 1: raise IndexError else: raise TypeError
# Реализуем функциональность контейнера-списка def __getitem__(self, i): self.__iscorrectindex(i) return self.__s[i] def __setitem__(self, i, v): self.__iscorrectindex(i) self.__s[i] = v def __delitem__(self, i): self.__iscorrectindex(i) del self.__s[i] def __contains__(self, v): return v in self.__s
Глава 15. Итераторы, контейнеры и перечисления
281
Проверим свеженаписанный класс в действии:
>>> s = MutableString("Python")
>>> print(s[-1]) n
>>> s[0] = "J"
>>> print(s)
Jython
>>> del s[2:4]
>>> print(s)
Juon
Теперь проверим, как наш класс обрабатывает нештатные ситуации. Введем вот такой код, обращающийся к элементу с несуществующим индексом:
>>> s[9] = "u"
В ответ интерпретатор Python выдаст вполне ожидаемое сообщение об ошибке:
Traceback (most recent call last):
File "
", line 1, in
File "D:/Data/Документы/Работа/Книги/Python 3 и PyQt 5 Разработка приложений
II/Примеры/15/15.3.py", line 36, in __setitem__ self.__iscorrectindex(i)
File "D:/Data/Документы/Работа/Книги/Python 3 и PyQt 5 Разработка приложений
II/Примеры/15/15.3.py", line 27, in __iscorrectindex raise IndexError
IndexError
15.2.2. Контейнеры-словари
Класс, реализующий функциональность перечисления, должен переопределять уже знако- мые нам методы:
__getitem__()
,
__setitem__()
,
__delitem__()
и
__contains__()
. Разумеет- ся, при этом следует сделать поправку на то, что вместо индексов здесь будут использо- ваться ключи произвольного типа (как правило, строкового).
Давайте исключительно для практики напишем класс
Version
, который будет хранить но- мер версии интерпретатора Python, разбитый на части: старшая цифра, младшая цифра и подрелиз, при этом доступ к частям номера версии будет осуществляться по строковым ключам, как в обычном словаре Python (листинг 15.4). Ради простоты чтения кода функ- циональность итератора реализовывать не станем, а также заблокируем операцию удаления элемента словаря, возбудив в методе
__delitem__()
исключение
TypeError
Листинг 15.4. Класс Version class Version: def __init__(self, major, minor, sub): self.__major = major # Старшая цифра self.__minor = minor # Младшая цифра self.__sub = sub # Подверсия def __str__(self): return str(self.__major) + "." + str(self.__minor) + "." +
str(self.__sub)
282
Часть I. Основы языка Python
# Реализуем функциональность словаря def __getitem__(self, k): if k == "major": return self.__major elif k == "minor": return self.__minor elif k == "sub": return self.__sub else: raise IndexError def __setitem__(self, k, v): if k == "major": self.__major = v elif k == "minor": self.__minor = v elif k == "sub": self.__sub = v else: raise IndexError def __delitem__(self, k): raise TypeError def __contains__(self, v): return v == "major" or v == "minor" or v == "sub"
Чтобы наш новый класс не бездельничал, дадим ему работу, введя такой код:
>>> v = Version(3, 6, 3)
>>> print(v["major"])
3
>>> v["sub"] = 4
>>> print(str(v))
3.6.4
Как видим, все работает как надо.
15.3. Перечисления
Перечисление — это определенный самим программистом набор каких-либо именованных значений. Обычно они применяются для того, чтобы дать понятные имена каким-либо зна- чениям, используемым в коде программы, — например, кодам ошибок, возвращаемых функциями Windows API.
1 ... 23 24 25 26 27 28 29 30 ... 83
Для создания перечислений применяются два класса, определенные в модуле 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