Путь поиска оператора import
Перед тем, как идти дальше, я хочу вкратце рассказать о путях поиска библиотек. Когда вы пытаетесь импортировать модуль (с помощью оператора import), Python ищет его в нескольких местах. В частности, он ищет во всех директориях, перечисленных в sys.path. Это просто список, который можно легко просматривать и изменять при помощи стандартных списочных методов. (Вы узнаете больше о списках в главе Встроенные типы данных.)
>>> import sys ①
>>> sys.path ②
['',
'/usr/lib/python31.zip',
'/usr/lib/python3.1',
'/usr/lib/python3.1/plat-linux2@EXTRAMACHDEPPATH@',
'/usr/lib/python3.1/lib-dynload',
'/usr/lib/python3.1/dist-packages',
'/usr/local/lib/python3.1/dist-packages']
>>> sys ③
<module 'sys' (built-in)>
>>> sys.path.insert(0, '/home/mark/diveintopython3/examples') ④
>>> sys.path ⑤
['/home/mark/diveintopython3/examples',
'',
'/usr/lib/python31.zip',
'/usr/lib/python3.1',
'/usr/lib/python3.1/plat-linux2@EXTRAMACHDEPPATH@',
'/usr/lib/python3.1/lib-dynload',
'/usr/lib/python3.1/dist-packages',
'/usr/local/lib/python3.1/dist-packages']
① | Импортирование модуля sys делает доступными все его функции и атрибуты. |
② | sys.path — список имён директорий, определяющий текущий путь поиска. (Ваш будет выглядеть иначе, в зависимости от вашей операционной системы, от используемой версии Python и от того, куда он был установлен.) Python будет искать в этих директориях (в заданном порядке) файл с расширением «.py», имя которого совпадает с тем, что вы пытаетесь импортировать. |
③ | Вообще-то я вас обманул; истинное положение дел немного сложнее, потому что не все модули лежат в файлах с расширением «.py». Некоторые из них, как, например, модуль sys, являются встроенными; они впаяны в сам Python. Встроенные модули ведут себя точно так же, как обычные, но их исходный код недоступен, потому что они не были написаны на Python! (Модуль sys написан на Си.) |
④ | Можно добавить новую директорию в путь поиска, добавив имя директории в список sys.path, во время выполнения Python, и тогда Python будет просматривать её наравне с остальными, как только вы попытаетесь импортировать модуль. Новый путь поиска будет действителен в течение всего сеанса работы Python. |
⑤ | Выполнив команду sys.path.insert(0, новый_путь), вы вставили новую директорию на первое место в список sys.path, и, следовательно, в начало пути поиска модулей. Почти всегда, именно это вам и нужно. В случае конфликта имён (например, если Python поставляется со 2-й версией некоторой библиотеки, а вы хотите использовать версию 3) этот приём гарантирует, что будут найдены и использованы ваши модули, а не те, которые идут в комплекте с Python. |
Всё является объектом
Если вы вдруг пропустили, я только что сказал, что функции в Python имеют атрибуты, и эти атрибуты доступны во время выполнения. Функция, как и всё остальное в Python, является объектом.
Запустите интерактивную оболочку Python и повторяйте за мной:
>>> import humansize ①
>>> print(humansize.approximate_size(4096, True)) ②
4.0 KiB
>>> print(humansize.approximate_size.__doc__) ③
Преобразует размер файла в удобочитаемую для человека форму.
Ключевые аргументы:
size -- размер файла в байтах
a_kilobyte_is_1024_bytes -- если True (по умолчанию), используются степени 1024
если False, используются степени 1000
Возвращает: текстовую строку (string)
① | Первая строчка импортирует программу humansize в качестве модуля — фрагмента кода, который можно использовать интерактивно или из другой Python-программы. После того, как модуль был импортирован, можно обращаться ко всем его публичным функциям, классам и атрибутам. Импорт применяется как в модулях, для доступа к функциональности других модулей, так и в интерактивной оболочке Python. Это очень важная идея, и вы ещё не раз встретите её на страницах этой книги. |
② | Когда вы хотите использовать функцию, определённую в импортированном модуле, нужно дописать к её имени название модуля. То есть вы не можете использовать просто approximate_size, обязательно humansize.approximate_size. Если вы использовали классы в Java, то для вас это должно быть знакомо. |
③ | Вместо того, чтобы вызвать функцию (как вы, возможно, ожидали), вы запросили один из её атрибутов — __doc__. |
![]() | Оператор import в Python похож на require из Perl. После import в Python, вы обращаетесь к функциям модуля как модуль.функция; после require в Perl, для обращения к функциям модуля используется имя модуль::функция. |
Что такое объект?
В языке Python всё является объектом, и у любого объекта могут быть атрибуты и методы. Все функции имеют стандартный атрибут __doc__, содержащий строку документации, определённую в исходном коде функции. Модуль sys — тоже объект, имеющий (кроме прочего) атрибут под названием path. И так далее.
Но мы так и не получили ответ на главный вопрос: что такое объект? Разные языки программирования определяют «объект» по-разному. В одних считается, что все объекты должны иметь атрибуты и методы. В других, что объекты могут порождать подклассы. В Python определение ещё менее чёткое. Некоторые объекты не имеют ни атрибутов, ни методов, хотя и могли бы их иметь. Не все объекты порождают подклассы. Но всё является объектом в том смысле, что может быть присвоено переменной или передано функции в качестве аргумента.
Возможно, вы встречали термин «объект первого класса» в других книгах о программировании. В Python функции — объекты первого класса. Функцию можно передать в качестве аргумента другой функции. Модули — объекты первого класса. Весь модуль целиком можно передать в качестве аргумента функции. Классы — объекты первого класса, и отдельные их экземпляры — тоже объекты первого класса.
Это очень важно, поэтому я повторю это, на случай если вы пропустили первые несколько раз: всё в Python является объектом. Строки — это объекты. Списки — объекты. Функции — объекты. Классы — объекты. Экземпляры классов — объекты. И даже модули являются объектами.
Отступы
Функции в Python не имеют ни явных указаний begin и end, ни фигурных скобок, которые бы показывали, где код функции начинается, а где заканчивается. Разделители — только двоеточие (:) и отступы самого кода.
def approximate_size(size, a_kilobyte_is_1024_bytes=True): ①
if size < 0: ②
raise ValueError('число должно быть неотрицательным') ③
④
multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
for suffix in SUFFIXES[multiple]: ⑤
size /= multiple
if size < multiple:
return '{0:.1f} {1}'.format(size, suffix)
raise ValueError('число слишком большое')
① | Блоки кода определяются по их отступам. Под «блоками кода» я подразумеваю функции, блоки if, циклы for и while и т. д. Увеличение отступа начинает блок, а уменьшение — заканчивает. Ни скобок, ни ключевых слов. Это означает, что пробелы имеют важное значение, и их количество тоже. В этом примере код функции отбит четырьмя пробелами. Не обязательно здесь должны быть именно четыре пробела, просто их число должно быть постоянным. Первая встретившаяся строчка без отступа будет означать конец функции. |
② | За оператором if должен следовать блок кода. Если в результате вычисления условного выражения оно окажется истинным, то выполнится блок, выделенный отступом, в противном случае произойдёт переход к блоку else (если он есть). Обратите внимание, что вокруг выражения скобки не стоят. |
③ | Эта строчка находится внутри блока if. Оператор raise вызывает исключение (типа ValueError), но только если size < 0. |
④ | Это ещё не конец функции. Совсем пустые строки не считаются. Они могут повысить читаемость кода, но не могут служить разделителями блоков кода. Блок кода функции продолжается на следующей строке. |
⑤ | Оператор цикла for тоже начинает блок кода. Блоки кода могут содержать несколько строк, а именно — столько, сколько строк имеют такую же величину отступа. Этот цикл for содержит три строки кода. Других синтаксических конструкций для описания многострочных блоков кода нет. Просто делайте отступы, и будет вам счастье! |
После того, как вы переборете внутренние противоречия и проведёте пару ехидных аналогий с Фортраном, вы подружитесь с отступами и начнёте видеть их преимущества. Одно из главных преимуществ — то, что все программы на Python выглядят примерно одинаково, поскольку отступы — требование языка, а не вопрос стиля. Благодаря этому становится проще читать и понимать код на Python, написанный другими людьми.
![]() | В Python используются символы возврата каретки для разделения операторов, а также двоеточие и отступы для разделения блоков кода. В C++ и Java используются точки с запятой для разделения операторов и фигурные скобки для блоков кода. |
Исключения
Исключения (англ. exceptions — нештатные, исключительные ситуации, требующие специальной обработки) используются повсюду в Python. Буквально каждый модуль в стандартной библиотеке Python использует их, да и сам Python вызывает их во многих ситуациях. Вы ещё неоднократно встретите их на страницах этой книги.
Что такое исключение? Обычно это ошибка, признак того, что что-то пошло не так. (Не все исключения являются ошибками, но пока это не важно.) В некоторых языках программирования принято возвращать код ошибки, который вы потом проверяете. В Python принято использовать исключения, которые вы обрабатываете.
Когда происходит ошибка в оболочке Python, она выводит кое-какие подробности об исключении и о том, как это случилось, и всё. Это называется необработанным исключением. Когда это исключение было вызвано, не нашлось никакого программного кода, чтобы заметить его и обработать должным образом, поэтому оно всплыло на самый верхний уровень — в оболочку Python, которая вывела немного отладочной информации и успокоилась. В оболочке это не так уж страшно, однако если это произойдёт во время работы настоящей программы, то вся программа с грохотом упадёт, если исключение не будет обработано. Может быть это то, что вам нужно, а может, и нет.
![]() | В отличие от Java, функции в Python не содержат объявлений о том, какие исключения они могут вызывать. Вам решать, какие из возможных исключений необходимо отлавливать. |
Результат исключения — это не всегда полный крах программы. Исключения можно обработать. Иногда исключения возникают из-за настоящих ошибок в вашем коде (например, доступ к переменной, которая не существует), но порой исключение — это нечто, что вы можете предвидеть. Если вы открываете файл, он может не существовать. Если вы импортируете модуль, он может быть не установлен. Если вы подключаетесь к базе данных, она может быть недоступна или у вас может быть недостаточно прав для доступа к ней. Если вы знаете, что какая-то строка кода может вызвать исключение, то его следует обработать с помощью блока try...except.
![]() | Python использует блоки try...except для обработки исключений и оператор raise для их генерации. Java и C++ используют блоки try...catch для обработки исключений и оператор throw для их генерации. |
Функция approximate_size() вызывает исключение в двух разных случаях: если переданный ей размер (size) больше, чем функция может обработать, или если он меньше нуля.
if size < 0:
raise ValueError('число должно быть неотрицательным')
Синтаксис вызова исключений достаточно прост. Надо написать оператор raise, за ним название исключения и опционально, поясняющую строку для отладки. Синтаксис напоминает вызов функции. (На самом деле, исключения реализованы как классы, и оператор raise просто создаёт экземпляр класса ValueError и передаёт в его метод инициализации строку 'число должно быть неотрицательным'. Но мы забегаем вперёд!)
![]() | Нет необходимости обрабатывать исключение в той функции, которая его вызвала. Если одна функция не обработает его, исключение передаётся в функцию, вызвавшую эту, затем в функцию, вызвавшую вызвавшую, и т. д. «вверх по стеку». Если исключение нигде не будет обработано, то программа упадёт, а Python выведет «раскрутку стека» (англ. traceback) в стандартный поток ошибок — и на этом конец. Повторяю, возможно, это именно то, что вам нужно, — это зависит от того, что делает ваша программа. |
Отлов ошибок импорта
Одно из встроенных исключений Python — ImportError (ошибка импорта), которое вызывается, если не удаётся импортировать модуль. Это может случиться по нескольким причинам, самая простая из которых — отсутствие модуля в пути поиска, оператора import. Что можно использовать для включения в программу опциональных возможностей. Например, библиотека chardet предоставляет возможность автоматического определения кодировки символов. Предположим, ваша программа хочет использовать эту библиотеку в том случае, если она есть, или спокойно продолжить работу, если пользователь не установил её. Можно сделать это с помощью блока try...except.
try: import chardet except ImportError: chardet = None |
После этого можно проверять наличие модуля chardet простым if:
if chardet:
# что-то сделать
else:
# продолжить дальше
Другое частое применение исключения ImportError — выбор из двух модулей, предоставляющих одинаковый интерфейс (API), причём применение одного из них предпочтительнее другого (может, он быстрее работает или требует меньше памяти). Для этого можно попытаться импортировать сначала один модуль, и если это не удалось , то импортировать другой. К примеру, в главе XML рассказывается о двух модулях, реализующих один и тот же API, так называемый ElementTree API. Первый — lxml — сторонний модуль, который необходимо скачивать и устанавливать самостоятельно. Второй — xml.etree.ElementTree — медленнее, но входит в стандартную библиотеку Python 3.
try:
from lxml import etree
except ImportError:
import xml.etree.ElementTree as etree
При выполнении этого блока try...except будет импортирован один из двух модулей под именем etree. Поскольку оба модуля реализуют один и тот же API, то в последующем коде нет необходимости проверять, какой из этих модулей был импортирован. И раз импортированный модуль в любом случае именуется как etree, то не придётся вставлять лишние if для обращения к разноимённым модулям.
Несвязанные переменные
Взглянем ещё раз на вот эту строчку функции approximate_size():
multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
Мы нигде не объявляли переменную multiple (множитель), мы только присвоили ей значение. Всё в порядке, Python позволяет так делать. Что он не позволит сделать, так это обратиться к переменной, которой не было присвоено значение. Если попытаться так сделать, возникнет исключение NameError (ошибка в имени).
>>> x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
>>> x = 1
>>> x
1
Перевод сообщения оболочки:
Раскрутка стека (список последних вызовов):
Файл "<stdin>", строка 1, <модуль>
NameError: имя 'x' не определено
Однажды вы скажете Python «спасибо» за это.