Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

[ Россум, Дрейк, Откидач ] Язык программирования Python

.pdf
Скачиваний:
282
Добавлен:
25.04.2014
Размер:
1.5 Mб
Скачать

8.6. “Страхование” от ошибок

81

... finally:

... print ’дП УЧЙДБОЙС!’

...

дП УЧЙДБОЙС!

Traceback (innermost last): File "<stdin>", line 2

KeyboardInterrupt

Ветвь finally выполняется независимо от того, возникла ли исключительная ситуация во время выполнения блока try или нет, в том числе и если выход происходит по инструкции break или return.

Инструкция try может иметь либо одну или несколько ветвей except, либо одну ветвь finally, но не оба варианта сразу.

82

Глава 9

Классы

Механизм классов языка Python использует минимум нового синтаксиса и семантики. Он представляет собой смесь механизмов классов C++ и Modula-3. Так же, как и модули, классы не возводят абсолютного барьера между определением и пользователем, а скорее полагаются на хороший стиль пользователя “не вламываться” в определение. Однако наиболее важные особенности классов полностью сохранены: механизм наследования допускает несколько базовых классов, производный класс может переопределить любые методы базовых классов, из метода можно вызывать метод с тем же именем базового класса. Объекты могут содержать произвольное количество собственных данных.

Говоря в терминологии C++, все атрибуты класса (включая поля данных) являются открытыми (public), а все методы — виртуальными (virtual). Как и в Modula-3, нет сокращения, позволяющего ссылаться на атрибуты объекта из его метода: функция-метод определяется с явным первым аргументом, представляющим сам объект, и подставляемым автоматически при вызове. Подобно Smalltalk, классы сами являются объектами, хотя и в более широком смысле этого слова: в языке Python все типы данных являются объектами. Это позволяет использовать общую семантику для импортирования и переименования, но встроенные типы (как и в C++ и Modula-3) нельзя использовать в качестве базовых классов. Кроме того, так же как в C++, но в отличие от Modula-3, можно переопределить большинство встроенных операций над экземплярами классов.

9.1Несколько слов о терминологии

По причине отсутствия универсальной терминологии, время от времени мы будем пользоваться терминами Smalltalk и C++. Также следует предупредить, что существует терминологическая ловушка для читателей, знакомых с объектно-ориентированным программированием: слово “объект” в языке Python совсем не обязательно означает экземпляр класса. Так, объекты встроенных типов, такие как целые числа, списки и даже некоторые экзотические, вроде файлов, тоже не являются экземплярами классов. Однако все объекты языка Python имеют общую часть семантики, лучше всего описываемую словом “объект”.

Один и тот же объект может быть привязан к нескольким именам, в том числе находящимся в различных пространствах имен (использование псевдонимов, aliasing). На это свойство обычно не обращают внимания при первом знакомстве, и его можно благополучно игнорировать, пока Вы работаете с неизменяемыми объектами (числа, строки, кортежи). Однако использование псевдонимов (преднамеренно!) отражается на семантике кода, работающего с изменяемыми объектами (списки, словари, файлы и т.

9.2. Области видимости и пространства имен

83

п.). Обычно из этого извлекают пользу, так как псевдонимы во многих отношениях работают аналогично указателям. Например, передача объекта в качестве аргумента достаточно эффективна, потому что реализована как передача указателя. Если функция изменяет объект, переданный в качестве аргумента, все изменения будут видны после возврата из функции — таким образом, отпадает необходимость в использовании двух различных механизмов передачи аргументов, как это сделано в языке Pascal.

9.2 Области видимости и пространства имен

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

Начнем с нескольких определений. Пространство имен определяет отображение имен в объекты. Большинство пространств имен в языке Python реализованы как словари, что, однако, никак себя не проявляет (кроме производительности) и может быть изменено в будущем. Вот несколько примеров пространств имен: множество встроенных имен (функции, исключения), глобальные имена в модуле, локальные имена при вызове функций. В этом смысле множество атрибутов объекта также образует пространство имен. Важно понимать, что между именами в разных пространствах имен нет связи. Например, два модуля могут определить функции с именем “maximize” не создавая при этом путаницы — пользователь должен ссылаться на них с использованием имени модуля в качестве приставки.

Под словом атрибут мы подразумеваем любое имя, следующее после точки: например, в выражении z.real, real является атрибутом объекта z. Строго говоря, имена в модулях являются атрибутами модуля: в выражении modname.funcname, modname является объектом-модулем и funcname является его атрибутом. В этом случае имеет место прямое соответствие между атрибутами модуля и глобальными именами, определенными в модуле: они совместно используют одно пространство имен1!

Атрибуты могут быть доступны только для чтения, а могут и допускать присваивание. Во втором случае Вы можете записать ‘modname.attribute = 42’ или даже удалить его, используя инструкцию del: ‘del modname.attribute’.

Пространства имен создаются в различные моменты времени и имеют разную продолжительность жизни. Пространство имен, содержащее встроенные имена, создается при запуске интерпретатора и существует все время его работы. Глобальное пространство имен модуля создается, когда он считывается, и, обычно, также существует до завершения работы интерпретатора. Инструкции, выполняемые на верхнем уровне, т. е. читаемые из файла-сценария или интерактивно, рассматриваются как часть модуля __main__, имеющего собственное глобальное пространство имен. (В действительности,

1Есть одно исключение. Объект-модуль имеет секретный атрибут __dict__, содержащий словарь, используемый для реализации пространства имен модуля. Имя __dict__ является атрибутом, однако не является глобальным именем. Не следует использовать атрибут __dict__ где-либо кроме отладчиков, так как это нарушает абстракцию реализации пространства имен.

84

Глава 9. Классы

встроенные имена также находятся в модуле — __builtin__.)

Локальное пространство имен функции создается при вызове функции и удаляется при выходе из нее (возвращается значение или генерируется исключение, которое не обрабатывается внутри функции). Безусловно, при рекурсивном вызове создается собственное локальное пространство имен для каждого вызова.

Область видимости — фрагмент программы, в котором пространство имен непосредственно доступно, то есть нет необходимости в использовании записи через точку для того, чтобы поиск имени производился в данном пространстве имен.

Несмотря на статическое определение, области видимости используются динамически. В любой момент времени выполнения программы используется ровно три вложенных области видимости (три непосредственно доступных пространства имен). Сначала поиск имени производится во внутренней области видимости, содержащей локальные имена. Далее — в средней, содержащей глобальные имена модуля. И, наконец, во внешней, содержащей встроенные имена.

Обычно локальная область видимости соответствует локальному пространству имен текущей функции (класса, метода). За пределами функции (класса, метода), локальная область видимости соответствует тому же пространству имен, что и глобальная: пространству имен текущего модуля.

Важно понимать, что область видимости определяется по тексту: глобальная область видимости функции, определенной в модуле — пространство имен этого модуля, независимо от того, откуда или под каким псевдонимом функция была вызвана. С другой стороны, реально поиск имен происходит динамически, во время выполнения. Однако язык развивается в сторону статического разрешения имен, определяемого во время “компиляции”, поэтому не стоит полагаться на динамическое разрешение имен! (Фактически, локальные переменные уже определяются статически.)

В языке Python есть особенность: присваивание всегда производится имени в локальной области видимости, если перед этим не было указано явно (инструкция global), что переменная находится в глобальной области видимости. Присваивание не копирует данные — оно только привязывает имя к объекту. То же самое верно и для удаления: инструкция ‘del x’ удаляет имя x из пространства имен, соответствующего локальной области видимости. В сущности, все операции, которые вводят новые имена, используют локальную область. Так, импортирование модуля и определение функции привязывают модуль или функцию к локальной области видимости.

9.3 Первый взгляд на классы

Классы требуют немного нового синтаксиса, добавляют три новых типа объектов и некоторое которое количество новой семантики.

9.3. Первый взгляд на классы

85

9.3.1Синтаксис определения класса

Простейшая модель определения класса выглядит следующим образом:

class ЙНС_ЛМБУУБ: ЙОУФТХЛГЙС1

.

.

.

ЙОУФТХЛГЙСN

Определение класса, подобно определению функции (инструкция class, как и def), должно быть выполнено перед тем, как класс можно будет использовать. (Предполагается, что Вы можете поместить определение класса в одну из ветвей инструкции if или в тело функции.)

На практике инструкции внутри определения класса обычно являются определениями функций, но разрешены, и иногда полезны, другие инструкции. Определение функции внутри класса имеет специфическую форму записи списка аргументов, продиктованную соглашениями по вызову методов. Мы вернемся к этим особенностям позже.

Когда выполняется определение класса, создается новое пространство имен и используется в качестве локальной области видимости при выполнении инструкций в теле определения. Таким образом, все присваивания производятся именам из нового пространства имен. В частности, определение функции создает соответствующее имя в пространстве имен класса.

По окончании выполнения определения функции, создается объект-класс. По существу он является “оболочкой” пространства имен, созданного определением класса. В следующем разделе мы расскажем об объектах-классах более подробно. Исходная область видимости (которая была перед выполнением определения класса) восстанавливается, и объект-класс привязывается к имени класса (в приведенном примере — ЙНС_ЛМБУУБ) в пространстве имен, соответствующему исходной области видимости.

9.3.2Объекты-классы

Объекты-классы поддерживают два вида операций: доступ к атрибутам и создание экземпляра класса.

Доступ к атрибутам объекта-класса осуществляется так же, как и для других объектов языка Python: obj.attrname. Действительными именами атрибутов являются все имена, помещенные в пространство имен класса при создании объекта-класса. Пусть определение класса выглядит следующим образом:

class MyClass:

’рТПУФПК РТЙНЕТ ЛМБУУБ’

86

Глава 9. Классы

i = 12345 def f(x):

return ’рТЙЧЕФ ЧУЕНХ НЙТХ’

Тогда i и f являются действительными атрибутами, ссылающимися на целое число и объект-метод соответственно. Атрибутам можно присваивать новые значения, например, Вы можете изменить значение MyClass.i. __doc__ также является действительным атрибутом, ссылающимся на строку документации класса: ’рТПУФПК РТЙНЕТ ЛМБУУБ’.

Создание экземпляра класса использует запись вызова функций. Просто считайте объект-класс функцией без параметров, возвращающей созданный экземпляр класса. Например,

x = MyClass()

создает новый экземпляр класса и присваивает его локальной переменной x.

В приведенном примере создается “пустой” объект. Во многих случаях необходимо создавать объект с определенным начальным состоянием — для этого класс должен содержать специальный метод __init__(), например:

class MyClass:

def __init__(self):

self.data = []

Если для класса определен метод __init__(), то он автоматически вызывается при создании каждого экземпляра этого класса.

Для большей гибкости метод __init__(), конечно, может иметь аргументы. В этом случае, аргументы, используемые при создании экземпляра класса, передаются методу __init__(). Например:

>>> class Complex:

...

def __init__(self, realpart, imagpart):

...

self.r =

realpart

...

self.i =

imagpart

...

 

 

>>>x = Complex(3.0, -4.5)

>>>x.r, x.i

(3.0, -4.5)

9.3.3Объекты-экземпляры

Что мы теперь можем делать с объектами-экземплярами? Основная операция, воспринимаемая объектом-экземпляром — доступ к его атрибутам. Атрибуты могут быть двух

9.3. Первый взгляд на классы

87

видов.

Первый — атрибуты данных. Они соответствуют “переменным экземпляра” в Smalltalk и “членам данных” в C++. Атрибуты данных не нужно декларировать: они возникают, когда им первый раз присваивают значение. Например, если x является экземпляром класса MyClass, определенного выше, следующий код выведет значение

16:

x.counter = 1

while x.counter < 10: x.counter = x.counter * 2

print x.counter del x.counter

Второй тип атрибутов — методы. Метод — это функция, “принадлежащая” объекту. В языке Python термин “метод” применим не только к экземплярам классов — другие объекты тоже могут иметь методы. Например, у объектов-списков есть методы append(), insert(), remove(), sort() и т. д. Однако ниже, если явно не указано другого, мы будем использовать термин “метод” для методов экземпляров (instance method).

Действительные имена методов объекта-экземпляра зависят от класса: все атрибуты класса, являющиеся объектами-функциями автоматически становятся методами при обращении к соответствующим атрибутам экземпляра2. Так, в нашем примере x.f является методом, привязанным к объекту x. Но x.f — это не то же самое, что и MyClass.f. В первом случае метод “знает” объект, к которому он применяется, во втором — он не привязан к какому-либо объекту и ведет себя аналогично функции.

9.3.4 Методы экземпляров классов

Обычно метод вызывают непосредственно:

x.f()

В нашем примере он вернет строку ’рТЙЧЕФ ЧУЕНХ НЙТХ’. Однако совсем не обязательно вызывать метод прямо. x.f является объектом-методом, и его можно сохранить для дальнейшего использования:

xf = x.f while 1:

print xf()

2Функция может быть записана в lambda-форме, однако другие объекты, поддерживающие вызов (класс или экземпляр класса, для которого определен метод __call__) не подходят.

88

Глава 9. Классы

будет выводить ’рТЙЧЕФ ЧУЕНХ НЙТХ’ до тех пор, пока выполнение не будет прервано.

Что же происходит при вызове метода? Вы могли заметить, что x.f() вызывается без аргумента, несмотря на то, что в определении аргумент указан. Что же случилось с аргументом? Конечно, Python генерирует исключение, если функцию, требующую аргумент, вызвать без него — даже если аргумент на самом деле не используется.

Вы могли уже догадаться: особенность методов состоит в том, что объект, для которого он применяется, передается в качестве первого аргумента. В нашем примере вызов x.f() полностью эквивалентен MyClass.f(x). В общем, вызов метода, привязанного к объекту, со списком из n аргументов полностью эквивалентен вызову соответствующего не привязанного метода (или функции) со списком аргументов, полученным добавлением объекта перед первым аргументом.

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

9.4 Выборочные замечания

Атрибуты экземпляров (обычно атрибуты данных) записываются поверх атрибутов классов (обычно методов) с тем же именем. Чтобы избежать конфликтов имен, которые могут привести к тяжело обнаружимым ошибкам, полезно использовать своего рода соглашение, позволяющее минимизировать вероятность конфликтов. Например: называть методы именами, начинающимися с заглавной буквы, добавлять небольшую приставку в начало имен атрибутов данных (возможно просто символ подчеркивания) или использовать глаголы для методов и существительные для атрибутов данных.

В некоторых случаях изменение атрибутов данных напрямую, а не с помощью специально предназначенных для этого методов, может привести к порче инварианта объекта и непригодности его для дальнейшего использования. Вы можете “спрятать” данные и, тем самым, защитить их от случайного изменения. Для этого в языке существует соглашение: все атрибуты, имена которых содержат не менее двух символов подчеркивания в начале и не более одного символа подчеркивания в конце, считаются частными и доступны только из методов объекта. (На самом деле, Вы можете получить доступ к частным атрибутам извне, используя специальное имя, однако такой доступ никак не назовешь случайным. Более подробно работа с частными атрибутами описана в разделе 9.6.) С другой стороны, модули расширения, написанные на C, могут полностью

9.4. Выборочные замечания

89

спрятать детали реализации и при необходимости полностью контролировать доступ.

Обычно первый аргумент в определении метода называют self. Это не более чем соглашение: имя self не имеет абсолютно никакого специального значения. Однако, следуя этому соглашению, Вы делаете код более понятным для других программистов. (Некоторые программы, например программы просмотра классов, рассчитаны на то, что пользователь всегда следует этому соглашению.)

Любой объект-функция, являющийся атрибутом класса, определяет метод экземпляров этого класса. Совсем не обязательно, чтобы определение функции находилось в определении класса: присваивание объекта-функции локальной переменной также будет работать. Например:

# пРТЕДЕМЕОЙЕ ЖХОЛГЙЙ ЪБ РТЕДЕМБНЙ ПРТЕДЕМЕОЙС ЛМБУУБ def f1(self, x, y):

return min(x, x+y)

class

C:

 

f

=

f1

def

g(self):

 

 

return ’рТЙЧЕФ ЧУЕНХ НЙТХ’

h

=

g

Теперь f, g и h являются атрибутами класса C и ссылаются на объекты функции и, следовательно, все они будут методами экземпляров класса C. Вы можете также использовать функцию, определенную с помощью оператора lambda. Заметим, что такая практика обычно только сбивает с толку.

Методы могут вызывать другие методы, как атрибуты аргумента self, например:

class Bag:

def __init__(self):

self.data = [] def add(self, x):

self.data.append(x) def addtwice(self, x):

self.add(x)

self.add(x)

Методы могут ссылаться на глобальные имена точно таким же способом, как и обычные функции. Глобальная область видимости, ассоциированная с методом — это глобальное пространство имен модуля, содержащего определение функции (так как определение функции обычно находится в определении класса, то — в глобальном пространстве имен модуля, содержащего определение класса). В то время как редко существует веская причина использовать глобальные данные в методе, глобальная область видимости все же находит множество разумных применений: использование импортированных в глобальную область видимости модулей, а также определенных в ней функций и классов.

90

Глава 9. Классы

Атрибуты классов ведут себя как статические атрибуты их экземпляров (то есть общие для всех экземпляров данного класса). Однако присвоить такому атрибуту новое значение Вы можете, только обратившись к нему как атрибуту того класса, в котором он определен (в противном случае Вы лишь создадите новый атрибут экземпляра с таким же именем).

9.5Наследование

Конечно, особенность языка не достойна называться “классом” без поддержки наследования. Определение производного класса с именем РТПЙЪЧПДОЩК_ЛМБУУ выглядит следующим образом:

class РТПЙЪЧПДОЩК_ЛМБУУ(ВБЪПЧЩК_ЛМБУУ): ЙОУФТХЛГЙС1

.

.

.

ЙОУФТХЛГЙСN

Базовый класс (ВБЪПЧЩК_ЛМБУУ) должен быть определен в области видимости, в которой находится определение производного класса (РТПЙЪЧПДОЩК_ЛМБУУ). Вместо имени базового класса можно использовать выражение, например, если базовый класс определен в другом модуле:

class РТПЙЪЧПДОЩК_ЛМБУУ(НПДХМШ.ВБЪПЧЩК_ЛМБУУ):

Определение производного класса выполняется так же, как и определение базового класса. Сконструированный объект-класс помнит базовый — он используется для разрешения имен атрибутов: если запрошенный атрибут не найден в классе, поиск продолжается в базовом классе. Это правило применяется рекурсивно, если базовый класс, в свою очередь, является производным от другого класса. В создании экземпляра производного класса нет ничего особенного: РТПЙЪЧПДОЩК_ЛМБУУ() порождает новый экземпляр класса.

Производные классы могут переопределить методы базовых классов. Метод базового класса, вызывающего другой определенный для него метод, может, на самом деле, вызывать метод производного класса, переопределившего этот метод (говоря в терминах C++, все методы в языке Python являются виртуальными).

Переопределяя метод в производном классе, Вы можете также захотеть вызвать метод базового класса с тем же именем. Это можно сделать напрямую: просто вызовите ‘ВБЪПЧЩК_ЛМБУУ.НЕФПД(self, УРЙУПЛ_БТЗХНЕОФПЧ)’, где ВБЪПЧЩК_ЛМБУУ

ссылка на базовый класс в том виде, в котором он доступен в текущей области видимости.

В языке Python есть ограниченная поддержка множественного наследования. Определения класса с несколькими базовыми классами выглядит следующим образом: