Читаем Питон - модули, пакеты, классы, экземпляры (СИ) полностью

Питон - модули, пакеты, классы, экземпляры (СИ)

Python - объектно-ориентированный язык сверхвысокого уровня. Python, в отличии от Java, не требует исключительно объектной ори…

Автор Неизвестeн

Компьютеры и Интернет / Программирование 18+

Описание класса создает новое пространство имен, в котором определяются статические переменные (в нашем примере это 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(), который принимает параметр - функцию сравнения. Если сравнение сложное, и зависит от внешних условий, в качестве функции можно передать заранее проинициализированный объект.

Похожие книги