Программирование на Python 3. Руководство издательство СимволПлюс
![]()
|
В заключение Эта глава была начата с рассмотрения нескольких разновидностей син таксиса, используемых для импортирования пакетов, модулей и объ ектов, находящихся внутри модулей. Мы отметили, что многие про граммисты предпочитают использовать синтаксис import importable, чтобы избежать конфликтов имен, и что не следует давать программам и модулям имена, совпадающие с модулями или каталогами Python верхнего уровня. Мы также обсудили пакеты Python. Пакеты – это обычные каталоги, содержащие файл __init__.py и один или более модулей .py. Файл __init__.py может быть пустым, но для поддержки синтаксиса from im portable import * мы можем создать в этом файле специальную пере Поверхно стное и глубокое копирование, стр. 173 В заключение 269 менную __all__, представляющую собой список имен в модуле. Кроме того, в файл __init__.py можно поместить любой программный код, выполняющий инициализацию. Также было отмечено, что пакеты мо гут вкладываться друг в друга, для чего достаточно просто создать подкаталоги, каждый из которых содержит свой собственный файл __init__.py Были описаны два нестандартных модуля. Первый из них предостав ляет всего несколько функций и имеет очень простые доктесты. Вто рой модуль более сложный, имеет свои собственные исключения, ис пользует возможность динамического создания функций с платформо зависимой реализацией, частные глобальные данные, более сложные доктесты и выполняет функцию инициализации. Примерно половина главы была посвящена обзору стандартной биб лиотеки языка Python. Было упомянуто несколько модулей, предна значенных для работы со строками, и представлена пара примеров ис пользования объектов io.StringIO. Один из примеров продемонстриро вал, как можно записать текст в файл либо с использованием встроен ной функции print(), либо с использованием метода объекта файла write() , и как можно использовать объект io.StringIO вместо настоя щего файла. В предыдущих главах мы обрабатывали аргументы ко мандной строки, непосредственно читая содержимое sys.argv, но при обзоре поддержки обработки аргументов командной строки, включен ной в библиотеку, мы познакомились с модулем optparse, который су щественно упрощает работу с аргументами командной строки, – далее мы широко будем использовать этот модуль. Была упомянута имеющаяся в языке превосходная поддержка рабо ты с числами, числовые типы в библиотеке, три модуля с математиче скими функциями, а также поддержка научных и инженерных вы числений, предоставляемая проектом SciPy. Коротко были описаны библиотечные и созданные сторонними разработчиками классы для работы с датой/временем, а также представлены примеры, демонстри рующие, как можно получить текущие дату и время и как выполнять преобразования между типом datetime.datetime и количеством секунд, прошедших от начала эпохи. Также были рассмотрены дополнитель ные типы коллекций и алгоритмы работы с упорядоченными последо вательностями, реализованные в стандартной библиотеке, наряду с несколькими примерами использования функций из модуля heapq. Были представлены модули поддержки различных способов кодирова ния файлов (не имеющих отношения к кодировкам символов), модули для работы со сжатыми файлами в наиболее популярных форматах ар хивирования, а также модули поддержки работы с аудиоданными. Был дан пример, демонстрирующий порядок использования кодиров ки Base64 для сохранения двоичных данных в файлах .py, а также программа, выполняющая распаковывание тарболлов. Библиотекой предоставляется существенная поддержка операций над файлами 270 Глава 5. Модули и каталогами, причем все эти операции реализованы в виде платфор монезависимых функций. В приведенных примерах было показано, как можно создать словарь с именами файлов в виде ключей и време нем последнего изменения в виде значений, а также продемонстриро вано, как выполнить рекурсивный обход дерева каталогов с целью вы явления дубликатов файлов, основываясь на их именах и размерах. Огромную долю библиотеки занимают модули для реализации сете вых взаимодействий. Мы очень коротко рассмотрели, что имеется в библиотеке, начиная от обычных сокетов (включая сокеты с шифро ванием трафика) до серверов TCP, UDP и HTTP и поддержки WSGI. Также были упомянуты модули, предназначенные для работы с cook ies, сценариями CGI и данными протокола HTTP, средства синтакси ческого анализа HTML, XHTML и адресов URL. Были упомянуты про чие модули, включая модули для работы с протоколом XMLRPC и высокоуровневыми протоколами, такими как TP и NNTP, а также поддержка работы с протоколом электронной почты SMTP, как на сто роне клиента, так и на стороне сервера, и поддержка протоколов IMAP4 и PO3 на стороне клиента. Помимо всего прочего была упомянута имеющаяся в составе библиоте ки мощная поддержка возможности записи и парсинга формата XML, включая парсеры DOM, SAX и дерева элементов, а также модуль expat. Был приведен пример использования модуля xml.etree.ElementTree. Также были упомянуты некоторые другие пакеты и модули, имею щиеся в библиотеке. Стандартная библиотека языка Python представляет собой чрезвычай но ценный ресурс, который позволит сэкономить массу сил и времени, и во многих случаях позволяет писать более короткие программы, опирающиеся на функциональные возможности, предоставляемые библиотекой. Кроме того, существуют еще буквально тысячи пакетов сторонних разработчиков, восполняющих любую нехватку возможно стей, которую можно обнаружить в стандартной библиотеке. Все эти предопределенные функциональные возможности позволяют нам со средоточиться на предметной стороне решаемой задачи, оставляя большую часть деталей реализации за библиотечными модулями. Этой главой заканчивается обсуждение фундаментальных принципов процедурного программирования. В последующих главах, и в частно сти в главе 8, мы познакомимся с более передовыми и более специали зированными приемами процедурного программирования, а в следую щей главе будут представлены приемы объектноориентированного программирования. Использование языка Python в качестве исключи тельно процедурного языка программирования вполне возможно и да же оправданно, особенно при создании небольших программ, но при разработке средних и крупных программ, собственных пакетов и мо дулей, а также для создания долгоживущих проектов, как правило, предпочтительнее использовать объектноориентированный подход. Упражнение 271 К счастью, все, о чем рассказывалось до сих пор, с успехом может при меняться и в объектноориентированном программировании, поэтому в следующих главах мы продолжим накапливать наши знания и навы ки, основываясь на уже заложенном фундаменте. Упражнение Напишите программу, демонстрирующую содержимое каталогов по добно тому, как это делает команда dir в Windows или ls в UNIX. Пре имущество наличия собственной программы отображения каталогов состоит в том, что мы можем заложить в нее предпочитаемые парамет ры по умолчанию и использовать одну и ту же программу в любой сис теме, не утруждая себя необходимостью запоминать различия между командами dir и ls. Программа должна иметь следующий интерфейс: Usage: ls.py [options] [path1 [path2 [... pathN]]] The paths are optional; if not given . is used. Options: h, help show this help message and exit H, hidden show hidden files [default: off] m, modified show last modified date/time [default: off] o ORDER, order=ORDER order by ('name', 'n', 'modified', 'm', 'size', 's') [default: name] r, recursive recurse into subdirectories [default: off] s, sizes show sizes [default: off] (Вывод программы был несколько изменен, чтобы уместить его в ши рину книжной страницы.) Ниже приводится пример вывода содержимого небольшого каталога с помощью команды ls.py – ms – os misc/ : 20070410 15:49:01 322 misc/chars.pyw 20070801 11:24:57 1,039 misc/pfabug.pyw 20071012 09:00:27 2,445 misc/test.lout 20070410 15:50:31 2,848 misc/chars.png 20080211 14:17:03 12,184 misc/abstract.pdf 20080205 14:22:38 109,788 misc/klmqtintro.lyx 20071213 12:01:14 1,359,950 misc/tracking.pdf misc/phonelog/ 7 files, 1 directory Мы использовали группировку ключей командной строки (она обраба тывается модулем optparse автоматически), но тот же самый эффект можно было бы получить, используя ключи по отдельности, напри мер, ls.py – m – s – os misc/ , или даже применив более плотную группи ровку, ls.py – msos misc/ , или используя длинные имена параметров, ls.py –– modified –– sizes –– order=size misc/ , или любую их комбинацию. 272 Глава 5. Модули Обратите внимание на наличие ключа, управляющего включением в вывод программы «скрытых» файлов или каталогов, имена которых начинаются с точки (.). Упражнение довольно сложное. Вам придется ознакомиться с доку ментацией к модулю optparse, чтобы узнать, как объявлять парамет ры, которые принимают значение True, и как определить фиксирован ный перечень параметров. Если пользователь определяет в вызове па раметр –– recursive , программа должна выполнить обход файлов (но не каталогов) с помощью функции os.walk(); в противном случае она должна использовать для получения списка файлов и каталогов функ цию os.listdir(). Еще один подводный камень – организация пропуска скрытых ката логов при рекурсии. Их можно удалять из списка dirs, возвращаемого os.walk() , и тем самым пропускать их, модифицируя список. Но будь те внимательны – не присваивайте новое значение непосредственно переменной dirs, поскольку это не повлияет на список, на который она ссылается, а просто (и совершенно бесполезно) заместит его. Подход, использованный в решении, основан на присваивании срезу всего спи ска, то есть dirs[:] = [dir for dir in dirs if not dir.startswith(".")]. Лучший способ группировки разрядов при отображении размеров файлов состоит в том, чтобы импортировать модуль locale, вызвать функцию locale.setlocale() для получения региональных настроек пользователя и ис пользовать спецификатор формата n. Общий размер про граммы ls.py, разбитой на четыре функции, будет состав лять около 130 строк. Функция locale. setloca le() , стр. 108 6 Объектноориентированное программирование Во всех предыдущих главах мы широко использовали объекты, но при этом наш стиль программирования был исключительно процедурным. Язык Python одновременно поддерживает различные стили програм мирования – он позволяет программировать в процедурном стиле, объектноориентированном стиле и функциональном стиле, а также допускает смешивание стилей в любых пропорциях, не вынуждая нас писать программы, придерживаясь какогото определенного стиля. Вполне возможно написать любую программу исключительно в проце дурном стиле, и для небольших программ (скажем, до 500 строк) это совершенно нормальный выбор. Но большинству программ, особенно средних и крупных, объектноориентированный стиль дает значи тельные преимущества. В этой главе рассматриваются все фундаментальные концепции и прие мы объектноориентированного программирования на языке Python. Первый раздел предназначен для тех, кто не обладает еще достаточ ным опытом, и для тех, у кого имеется опыт процедурного программи рования (на таких языках, как C или Fortran). Второй раздел начинает ся со знакомства с некоторыми проблемами, свойственными процедур ному программированию, которые могут быть решены с использовани ем объектноориентированного стиля. Затем коротко описываются особенности объектноориентированного программирования на языке Python и поясняется соответствующая терминология. Далее следуют два основных раздела главы. Второй раздел охватывает тему создания собственных типов данных, способных хранить единственный элемент (хотя сами элементы могут иметь множество атрибутов), а в третьем разделе рассказывается о соз дании собственных типов коллекций, которые способны хранить про • Объектноориентированный подход • Собственные классы • Собственные классы коллекций 274 Глава 6. Объектно/ориентированное программирование извольное число объектов любых типов. В этих разделах рассматрива ется большинство аспектов объектноориентированного программиро вания на языке Python, хотя обсуждение некоторых, более сложных тем отложено до главы 8. Объектноориентированный подход В этом разделе мы коснемся некоторых проблем, характерных для процедурного стиля программирования, на примере ситуации, когда в программе необходимо реализовать представление большого числа окружностей. Минимальные данные, необходимые для задания ок ружности, – это координаты ее центра (x, y) и радиус. Самое простое решение заключается в том, чтобы использовать для представления каждой окружности кортеж из трех элементов. Например: circle = (11, 60, 8) Один из недостатков такого подхода – в неочевидности назначения ка ждого элемента кортежа. Мы можем подразумевать (x, y, radius) или (radius, x, y) . Другой недостаток состоит в том, что обращаться к эле ментам кортежа мы можем только по их индексам. Если представить, что у нас имеется две функции, distance_from_origin(x, y) и edge_dis tance_from_origin(x, y, radius) , при обращении к ним нам потребуется использовать операцию распаковывания кортежа, представляющего окружность: distance = distance_from_origin(*circle[:2]) distance = edge_distance_from_origin(*circle) В обоих случаях предполагается, что кортеж имеет вид (x, y, radius). Эту проблему можно решить, зная порядок следования элементов и используя операцию распаковывания с применением именованного кортежа: import collections Circle = collections.namedtuple("Circle", "x y radius") circle = Circle(13, 84, 9) distance = distance_from_origin(circle.x, circle.y) Такой подход позволяет создавать трехэлементные кортежи типа Circ le с именованными атрибутами, что делает вызов функций более про стым и понятным, поскольку для обращения к элементам используют ся их имена. К сожалению, сама проблема при этом не исчезает. На пример, ничто не мешает созданию окружности с ошибочными значе ниями: circle = Circle(33, 56, 5) Окружность с отрицательным радиусом – это сущая бессмыслица, но в данном случае именованный кортеж circle будет создан без возбуж дения исключения, как если бы радиус был представлен обычной пе Объектно/ориентированный подход 275 ременной, в которой оказалось отрицательное значение. Ошибка будет обнаружена только при вызове функции edge_distance_from_origin() и только если эта функция проверяет значение радиуса на отрицатель ное значение. Такая невозможность выполнить проверку данных в мо мент создания объекта является, пожалуй, самым негативным аспек том исключительно процедурного подхода. Если нам потребуется изменять окружности, например перемещать их, изменяя координаты центра, или изменять их размеры, изменяя радиус, мы сможем воспользоваться методом collections.namedtup le_replace() : circle = circle._replace(radius=12) Но, так же, как и при создании кортежа типа Circle, ничто не предо хранит (и даже не предупредит) от установки ошибочных значений. Если в программе предусматривается возможность частого изменения параметров окружностей, мы могли бы ради удобства использовать та кой изменчивый тип данных как список: circle = [36, 77, 8] Но и это решение не обеспечивает никакой защиты от ошибочных дан ных, а лучшее, что можно сделать для доступа к элементам по именам, это создать несколько констант, чтобы иметь возможность записывать более осмысленные инструкции, такие как circle[RADIUS] = 5. Но ис пользование списков несет дополнительные проблемы, например, мы, на вполне законных основаниях, сможем вызвать метод circle.sort()! Как вариант, можно было бы использовать словарь, например, circle = dict(x=36, y=77, radius=8) , но и в этом случае нет никакой возможно сти проконтролировать значение радиуса и нет никакой защиты от вы зова методов, неприменимых к окружностям. Объектноориентированные концепции и терминология Все, что нам необходимо, – это способ упаковать данные, представ ляющие окружность, и некоторый способ ограничить круг методов, которые могут применяться к данным, чтобы возможны были только допустимые операции. Обе поставленные задачи могут быть решены за счет создания собственного типа данных Circle. Далее в этом разде ле мы увидим, как создать тип данных Circle, но сначала нам необхо димо познакомиться с некоторыми начальными сведениями и терми нологией. Не стоит беспокоиться, если сначала термины покажутся вам незнакомыми, они станут более понятными, когда мы перейдем к изучению примеров. Мы будем использовать термины класс, тип и тип данных как взаи мозаменяемые. В языке Python мы можем создавать собственные классы, полностью интегрированные в язык, которые могут использо 276 Глава 6. Объектно/ориентированное программирование ваться как любые встроенные типы данных. Мы уже сталкивались со многими классами, например, dict, int и str. Мы будем использовать термин объект и иногда экземпляр, для обозначения экземпляра оп ределенного класса. Например, значение 5 – это объект класса int, а "oblong" – это объект класса str. Большинство классов инкапсулируют не только данные, но и методы, применяемые к этим данным. Например, класс str хранит строки сим волов Юникода в виде данных и поддерживает методы, такие как str.upper() . Многие классы также поддерживают дополнительные осо бенности, например, мы можем объединить две строки (или любые две последовательности), используя оператор +, и определить длину после довательности с помощью встроенной функции len(). Такие особенно сти реализуются при помощи специальных методов, которые пред ставляют собой самые обычные методы, за исключением того, что их имена всегда начинаются и заканчиваются двумя символами подчер кивания и являются предопределенными. Например, если нам потре буется создать класс, поддерживающий операцию конкатенации с ис пользованием оператора + и функцию len(), мы сможем обеспечить такую поддержку, реализовав в нашем классе специальные методы __add__() и __len__(). Напротив, мы никогда не должны использовать имена, начинающиеся и заканчивающиеся двумя символами подчер кивания, если они не являются предопределенными именами специ альных методов и не соответствуют назначению класса. Тем самым га рантируется, что мы никогда не вступим в конфликты с последующи ми версиями Python, даже если появятся новые предопределенные специальные методы. Как правило, объекты имеют атрибуты, методы – это вызываемые ат рибуты, а другие атрибуты – это данные. Например, объект complex имеет атрибуты imag и real и множество методов, включая специаль ные методы, такие как __add__() и __sub__() (поддержка двухместных операторов + и – ), и обычные методы, такие как conjugate(). Атрибуты данных (часто их называют просто «атрибутами») обычно реализуют ся как переменные экземпляра, то есть переменные, уникальные для каждого конкретного объекта. Мы еще увидим примеры обычных ат рибутов, а также примеры реализации атрибутов данных в виде свойств . Свойство – это элемент данных объекта, доступ к которым оформляется как доступ к переменной экземпляра, но само обращение неявно обслуживается методами доступа. Как будет показано ниже, использование свойств упрощает проверку корректности данных. Внутри метода (который является обычной функцией, получающей в виде первого аргумента конкретный экземпляр класса, в контексте которого выполняются действия) потенциально доступны несколько разновидностей переменных. К переменным экземпляра можно обра щаться посредством квалификации их имен самим экземпляром. Внутри методов могут создаваться локальные переменные – доступ |