355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Олег Бройтман » Питон - модули, пакеты, классы, экземпляры. » Текст книги (страница 1)
Питон - модули, пакеты, классы, экземпляры.
  • Текст добавлен: 6 октября 2016, 03:02

Текст книги "Питон - модули, пакеты, классы, экземпляры."


Автор книги: Олег Бройтман



сообщить о нарушении

Текущая страница: 1 (всего у книги 1 страниц)

Питон – модули, пакеты, классы, экземпляры.
[Олег Бройтман

[Закрыть]
]

Модули – структурирование пространства имен

При создании больших программ или библиотек большим количеством людей встает проблема коллизий имен. Питон решает эту проблему так же, как и большинство современных языков – структурированием пространства имен с помощью иерархически организованных модулей.

В Питоне три пространства имен: встроенное пространство имен (им можно управлять с помощью модуля доступа к интерпретатору sys), локальное пространство функции, и глобальное пространство модуля. (Объектно-ориентированное программирование создает дополнительные пространства классов и экземпляров классов, об этом ниже). Каждое пространство имен – это список отображений имени в значение.

Модуль – это совокупность описаний, объединенных в общее пространство имен – глобальное пространство модуля. Модули подключаются к программе (или другому модулю) с помощью оператора import, после которого имена из пространства имен модуля становятся доступными. Какие именно имена становятся доступны, определяет оператор import: вариант import module делает доступным ровно одно имя – имя модуля module, но зато через это имя можно использовать все глобальные имена модуля в виде module.name. В варианте from module import name из модуля импортируется указанное имя или список имен. В варианте from module import * из модуля импортируются все имена. Хотя автор модуля может ограничить этот список, а в отсутствии такого ограничения не импортируются имена, начинающиеся с подчеркивания – считается, что это внутренние имена модуля, не входящие в его публичный интерфейс.

Модуль может быть написан на Python, C или C++. Модули, написанные на Питоне, позволяют создавать новые классы (об объектно-ориентированном программировании речь будет идти ниже). Модули написанные на C и C++ позволяют создавать новые типы данных. Модули, написанные на C/C++ могут быть встроенные (builtin) или подгружаемые (DLL в Windows, разделяемые библиотеки в тех вариантах UNIX, в которых формат выполняемых файлов ELF).

Модуль на Питоне – это текстовый файл с расширением .py, содержащий описания переменных, функций и классов, плюс выполняемый код, который позволяет инициализировать модуль. Этот код выполняется при первом импорте модуля, после чего интерпретатор запоминает, что модуль уже проимпортирован и проинициализирован, и при последующих импортах этого же модуля код инициализации не выполняется.

Модули можно объединять в древовидные иерархии. Например, пакет XML содержит в себе пакеты DOM, SAX, Parsers (и другие, в зависимости от реализации). В результате можно проимпортировать PyExpat командой import xml.parsers.expat, тогда команды этого модуля будут доступны как xml.parsers.expat.ParserCreate, а можно проимпортировать его же командой from xml.parsers import expat, тогда команды этого модуля будут доступны как expat.ParserCreate. Или сразу from xml.parsers.expat import ParserCreate!

Объектно-ориентированное программирование

Питон – объектно-ориентированный язык со множественным наследованием. Можно сказать, что Питон поддерживает классическую ОО-модель с некоторыми особенностями. Классы в Python могут иметь статические переменные, разделяемые всеми экземплярами класса, но не могут иметь статических методов. Все методы относятся к экземплярам класса. Все методы можно переопределять в наследниках. Ссылка на объект (экземпляр класса) передается в методы в явном виде, в первом параметре. Традиционно эту переменную называют self. Какого-то общего предка всех классов (типа Object) в Python нет. Вообще в ОО-программировании в Питоне важно не кто от кого наследуется, а какой поддерживается интерфейс; наследование лишь дает реализацию. Формальных механизмов проверки интерфейсов пока нет, но возможно они будут включены в язык и библиотеки; Zope делает шаги

[Закрыть]
в этом направлении.

Конструктор и деструктор класса называются __init__ и __del__ (встроенные и служебные имена в Питоне обозначаются двумя подчеркиваниями перед и после имени; это всего лишь соглашение, язык не запрещает программисту писать собственные методы с такими именами). Вернее было бы назвать эти методы initializer и finalizer – они сами не размещают и не освобождают память (это делает за них интерпретатор), они инициализируют и очищают свои переменные.

В Питоне нет отдельного оператора new для создания экземпляров класса. Для создания экземпляра класса вызывается класс с необходимыми параметрами. Эти параметры передаются в __init__. Метод __del__, конечно, вызывается без параметров (кроме, естественно, self). Для удаления объектов (и не только экземпляров классов) в Питоне есть оператор del.

Пример.

class Foo:

bar = "baz"

def __init__(self, foo):

self.foo = foo

def __del__(self):

del self.foo

foo = Foo(12)

del foo

Описание класса создает новое пространство имен, в котором определяются статические переменные (в нашем примере это bar) и методы. Создание экземпляра порождает пространство имен объекта, доступ к которому осуществляется через переменную экземпляра класса foo, а внутри методов класса – через переменную self.

Классы в Питоне позволяют программисту создавать новые типы данных и определять для них все операции, доступные для встроенных типов. Например, метод __getitem__ позволяет индексировать объект, а __setitem__ – присваивать индексу объекта. Метод __getitem__ также позволяет объекту участвовать в цикле for, эмулируя последовательность (sequence). Есть методы, позволяющие объекту эмулировать булевские значения и участвовать в операторах if и while. Методы __getattr__ и __setattr__ позволяют читать и писать атрибуты объектов. Метод __call__ позволяет вызывать экземпляр класса с параметрами!

Python позволяет переопределить все инфиксные операции, причем отдельно для левого и правого аргумента выражения. Например, если a – экземпляр класса A, и b – экземпляр класса B, то для вычисления выражения a + b Питон будет сначала искать метод __add__ в классе A, а если не найдет – то метод __radd__ в классе B (а если и там не найдет – возбудит исключение TypeError).

Многие программисты, особенно писавшие на C++, боятся и не любят множественного наследования. Авторы языка Java вообще не включили множественное наследование в язык. Совершенно напрасно! Python позволяет использовать множественное наследование весьма успешно и удобно. Множественное наследование облегчает переиспользование кода (code reuse) вместо copy/paste-программирования, что очень важно и для эффективности, и для читаемости программ, и для отладки. Часто программисты на Питоне создают класс с помощью множественного наследования из нескольких связанных между собой "кирпичиков", словно из конструктора. Такие "кирпичики" в ОО-программировании называются MixIn-классами. Подробную статью про программирование с помощью MixIn-классов можно прочесть в Linux Journal

[Закрыть]

Еще один способ использования классов (точнее, экземпляров), не связанный непосредственно с ОО-программированием – использование пространства имен, которое предоставляет объект. Рассмотрим следующую проблему. Вам надо пройти циклом по списку, сохраняя между итерациями цикла некоторую информацию. Это можно сделать циклом for, никаких проблем. А можно воспользоваться возможностями функционального программирования, которые есть в Питоне – функциями map, filter, reduce и тому подобное. Эти функции требую в качестве первого параметра функцию, которую они в процессе цикла вызывают. Это эффективнее, чем цикл for (эти функции-то написаны на C), но возникает проблема с хранением состояния между итерациями. Функция, которую вызывает map может хранить состояние только в глобальных переменных. Для простых программ это вполне приемлемо. Но вот, скажем, с многопоточными программами будут проблемы – необходимо запирать и синхронизировать доступ к глобальным переменным. Да и вообще к глобальным переменным надо обращаться только при крайней нужде.

Вот тут на помощь приходит дополнительное пространство имен, существующее в экземпляре класса. Создадим класс

class Process:

def __init__(self):

self.foo = 0

def __call__(self, v):

if self.foo > 100:

raise OverflowError

self.foo += v

return self.foo

Создадим экземпляр этого класса: p = Process(), и передадим этот объект в map вместо функции: result = map(p, sequence). Функция map, ничего не подозревая, будет вызывать переданный ей объект как функцию с одним параметром. Никаких проблем – мы так описали класс, что его экземпляры можно вызывать, и именно с одним параметром! И от итерации к итерации объект p сохраняет необходимое состояние.

Другой похожий пример:

class Process:

def __init__(self):

self.sum = 0

def add(self, v):

self.sum += v

return self.sum

p = Process()

result = map(p.add, sequence)

print p.sum

Вся разница в этом примере – мы передаем не объект p, а его метод p.add. Но что такое p.add? В Python это особая сущность, называемая BoundMethod. Это объект, который помнит адрес объекта p, адрес функции add класса Process, и, когда его вызывают, в свою очередь вызывает метод класса с правильным первым параметром self. Если обратиться к этому методу как Process.add, то это – UnboundMethod, и его надо вызывать, подставив все параметры в явном виде: Process.add(p, 1). Вызов в таком виде часто используется для вызова родительского конструктора или метода:

class Foo(Bar)

def __init__(self):

Bar.__init__(self)

Еще один вариант использования этого трюка – сортировка списков. Списки в Питоне имеют метод sort(), который принимает параметр – функцию сравнения. Если сравнение сложное, и зависит от внешних условий, в качестве функции можно передать заранее проинициализированный объект.


    Ваша оценка произведения:

Популярные книги за неделю