Содержание страницы
Классы¶Classes
Механизм классов Python добавляет в язык классы с минимальным количеством нового синтаксиса и семантики. Он представляет собой смесь механизмов классов из C++ и Modula-3. Как и в случае с модулями, классы в Python не устанавливают абсолютного барьера между определением и пользователем, а полагаются на вежливость пользователя, чтобы не «взламывать определение». Однако самые важные возможности классов сохраняются в полной мере: механизм наследования классов допускает несколько базовых классов, производный класс может переопределять любые методы своего базового класса или классов, а метод может вызывать метод базового класса с тем же именем. Объекты могут содержать произвольный объём приватных данных.
В терминологии C++ члены класса (включая данные-члены) обычно являются public (за исключением, см. ниже Приватные переменные), а все функции-члены являются virtual. Специальных конструкторов или деструкторов нет. Как и в Modula-3, нет сокращений для обращения к членам объекта из его методов: функция-метод объявляется с явным первым аргументом, представляющим объект, который неявно передаётся при вызове. Как и в Smalltalk, сами классы являются объектами, хотя и в более широком смысле слова: в Python все типы данных – это объекты. Это даёт семантику для импорта и переименования. В отличие от C++ и Modula-3, встроенные типы можно использовать как базовые классы для расширения пользователем. Также, как в C++, но в отличие от Modula-3, большинство встроенных операторов со специальным синтаксисом (арифметические операторы, индексация и т.д.) можно переопределять для экземпляров классов.
Несколько слов о терминологии¶A Word About Terminology
Из-за отсутствия общепринятой терминологии для обсуждения классов я иногда буду использовать термины Smalltalk и C++. (Я бы использовал термины Modula-3, так как его объектно-ориентированная семантика ближе к Python, чем C++, но полагаю, что немногие читатели о нём слышали.)
Объекты обладают индивидуальностью, и несколько имён (в разных областях видимости) могут быть привязаны к одному и тому же объекту. В других языках это называется алиасингом. Обычно это не бросается в глаза при первом знакомстве с Python, и об этом можно безопасно забыть при работе с неизменяемыми базовыми типами (числа, строки, кортежи). Однако алиасинг оказывает (преднамеренное!) влияние на семантику кода Python, работающего с изменяемыми объектами, такими как списки, словари и большинство типов, представляющих сущности вне программы (файлы, окна и т.д.). Обычно это используется на благо программы, поскольку алиасы в некоторых отношениях ведут себя как указатели. Например, передача объекта обходится дёшево, так как реализация передаёт только указатель; и если функция изменяет объект, переданный в качестве аргумента, вызывающий увидит изменения – это устраняет необходимость в двух разных механизмах передачи аргументов, как в Pascal.
Области видимости и пространства имён Python¶Python Scopes and Name Spaces
Прежде чем ввести классы, я должен рассказать кое-что о правилах областей видимости в Python. Определения классов используют некоторые хитрости с пространствами имён, и нужно знать, как работают области видимости и пространства имён, чтобы полностью понимать происходящее. Кстати, знание этого предмета полезно любому продвинутому программисту на Python.
Начнём с некоторых определений.
Пространство имён – это отображение имён на объекты. Большинство пространств имён в настоящее время реализованы как словари Python, но это обычно никак не заметно (кроме производительности) и может измениться в будущем. Примеры пространств имён: набор встроенных имён (функции, такие как abs(), и имена встроенных исключений); глобальные имена в модуле; локальные имена при вызове функции. В некотором смысле набор атрибутов объекта также образует пространство имён. Важно знать, что между именами в разных пространствах имён нет абсолютно никакой связи; например, два разных модуля могут определить функцию «maximize» без путаницы – пользователи модулей должны указывать перед ней имя модуля.
Между прочим, я использую слово атрибут для любого имени после точки – например, в выражении z.real, real является атрибутом объекта z. Строго говоря, ссылки на имена в модулях – это ссылки на атрибуты: в выражении modname.funcname, modname является объектом модуля, а funcname – его атрибут. В этом случае существует прямое отображение между атрибутами модуля и глобальными именами, определёнными в модуле: они используют одно и то же пространство имён! [1]
Атрибуты могут быть только для чтения или для записи. В последнем случае возможно присваивание атрибутам. Атрибуты модуля доступны для записи: можно написать modname.the_answer = 42. Атрибуты, доступные для записи, также можно удалить с помощью оператора del. Например, del modname.the_answer удалит атрибут the_answer из объекта с именем modname.
Пространства имён создаются в разные моменты и имеют разное время жизни. Пространство имён, содержащее встроенные имена, создаётся при запуске интерпретатора Python и никогда не удаляется. Глобальное пространство имён модуля создаётся при чтении определения модуля; обычно пространства имён модулей существуют до завершения интерпретатора. Инструкции, выполняемые при верхнеуровневом вызове интерпретатора (из скрипта или в интерактивном режиме), считаются частью модуля __main__, поэтому они имеют собственное глобальное пространство имён. (Встроенные имена на самом деле тоже живут в модуле; он называется builtins.)
Локальное пространство имён для функции создаётся при её вызове и удаляется, когда функция завершается или возбуждает исключение, не обработанное внутри функции. (На самом деле, «забывание» было бы лучшим способом описать происходящее.) Конечно, рекурсивные вызовы имеют собственные локальные пространства имён.
Область видимости – это текстовая область программы на Python, в которой пространство имён доступно напрямую. «Напрямую доступно» означает, что неквалифицированная ссылка на имя пытается найти это имя в пространстве имён.
Хотя области видимости определяются статически, они используются динамически. В любой момент во время выполнения существует как минимум три вложенных области видимости, чьи пространства имён непосредственно доступны: самая внутренняя область (проверяется первой) содержит локальные имена; пространства имён любых охватывающих функций (проверяются начиная с ближайшей охватывающей области); средняя область (проверяется следующей) содержит глобальные имена текущего модуля; и самая внешняя область (проверяется последней) – это пространство имён встроенных имён.
Если имя объявлено как global, все ссылки и присваивания направляются прямо в среднюю область видимости, содержащую глобальные имена модуля. Для перепривязки переменных, найденных вне самой внутренней области видимости, можно использовать оператор nonlocal; если не объявлено nonlocal, такие переменные доступны только для чтения (попытка записи в такую переменную просто создаст новую локальную переменную во внутренней области видимости, оставив внешнюю переменную с тем же именем без изменений).
Обычно локальная область видимости ссылается на локальные имена (текстуально) текущей функции. Вне функций локальная область видимости ссылается на то же пространство имён, что и глобальная: пространство имён модуля. Определения классов помещают ещё одно пространство имён в локальную область.
Важно понимать, что области видимости определяются текстуально: глобальная область функции, определённой в модуле, – это пространство имён этого модуля, независимо от того, откуда и под каким псевдонимом функция вызывается. С другой стороны, фактический поиск имён выполняется динамически, во время выполнения – однако определение языка развивается в сторону статического разрешения имён, во время «компиляции», так что не полагайтесь на динамическое разрешение имён! (На самом деле, локальные переменные уже определяются статически.)
Особая причуда Python заключается в том, что если не действует оператор global или nonlocal, то присваивания именам всегда попадают в самую внутреннюю область видимости. Присваивания не копируют данные – они просто привязывают имена к объектам. То же самое верно для удалений: оператор del x удаляет привязку x из пространства имён, на которое ссылается локальная область. Фактически все операции, вводящие новые имена, используют локальную область: в частности, операторы import и определения функций привязывают имя модуля или функции в локальной области. (Оператор global можно использовать, чтобы указать, что определённые переменные находятся в глобальной области.)
Оператор global используется, чтобы указать, что определённые переменные живут в глобальной области видимости и должны перепривязываться там; оператор nonlocal указывает, что определённые переменные живут в объемлющей области видимости и должны перепривязываться там.
Пример областей видимости и пространств имён¶Scopes and Namespaces Example
Это пример, демонстрирующий, как ссылаться на разные области видимости и пространства имён, а также как global и nonlocal влияют на привязку переменных:
def scope_test():
def do_local():
spam = "local spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
spam = "test spam"
do_local()
print("After local assignment:", spam)
do_nonlocal()
print("After nonlocal assignment:", spam)
do_global()
print("After global assignment:", spam)
scope_test()
print("In global scope:", spam)
Вывод примера кода:
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
Обратите внимание, как локальное присваивание (по умолчанию) не изменило привязку scope_test для spam. Присваивание nonlocal изменило привязку scope_test для spam, а присваивание global изменило привязку на уровне модуля.
Также можно заметить, что до присваивания global для spam не было предыдущей привязки.
Первое знакомство с классами¶A First Look at Classes
Классы вводят немного нового синтаксиса, три новых типа объектов и некоторую новую семантику.
Синтаксис определения класса¶Class Definition Syntax
Простейшая форма определения класса выглядит так:
class ClassName:
<statement-1>
.
.
.
<statement-N>
Определения классов, как и определения функций (операторы def), должны быть выполнены, прежде чем они начнут действовать. (Можно, например, поместить определение класса в ветвь оператора if или внутрь функции.)
На практике операторы внутри определения класса обычно являются определениями функций, но допускаются и другие операторы, и иногда они полезны – мы вернёмся к этому позже. Определения функций внутри класса обычно имеют особую форму списка аргументов, продиктованную соглашениями о вызове методов – и это тоже объясняется позже.
При входе в определение класса создаётся новое пространство имён, которое используется как локальная область видимости – таким образом, все присваивания локальным переменным попадают в это новое пространство имён. В частности, определения функций связывают имя новой функции здесь.
Когда определение класса завершается нормально (доходит до конца), создаётся объект класса. По сути, это обёртка вокруг содержимого пространства имён, созданного определением класса; мы узнаем больше об объектах классов в следующем разделе. Исходная локальная область видимости (та, что действовала до входа в определение класса) восстанавливается, и объект класса привязывается к имени класса, указанному в заголовке определения класса (ClassName в примере).
Объекты класса¶Class Objects
Объекты классов поддерживают два вида операций: обращение к атрибутам и создание экземпляров.
Ссылки на атрибуты используют стандартный синтаксис, применяемый для всех ссылок на атрибуты в Python: obj.name. Допустимые имена атрибутов – это все имена, которые были в пространстве имён класса на момент создания объекта класса. Итак, если определение класса выглядело так:
class MyClass:
"""Простой пример класса"""
i = 12345
def f(self):
return 'hello world'
тогда MyClass.i и MyClass.f являются допустимыми ссылками на атрибуты, возвращающими целое число и объект функции соответственно. Атрибутам класса также можно присваивать значения, так что можно изменить значение MyClass.i через присваивание. __doc__ также является допустимым атрибутом, возвращающим строку документации, принадлежащую классу: "A simple example class".
Создание экземпляра класса использует синтаксис вызова функции. Просто представьте, что объект класса – это функция без параметров, возвращающая новый экземпляр класса. Например (предполагая приведённый выше класс):
x = MyClass()
создаёт новый экземпляр класса и присваивает этот объект локальной переменной x.
Операция создания экземпляра («вызов» объекта класса) создаёт пустой объект. Многие классы предпочитают создавать объекты с экземплярами, настроенными на определённое начальное состояние. Поэтому класс может определить специальный метод с именем __init__(), например так:
def __init__(self):
self.data = []
Когда класс определяет метод __init__(), создание экземпляра класса автоматически вызывает __init__() для только что созданного экземпляра класса. Таким образом, в этом примере новый инициализированный экземпляр можно получить так:
x = MyClass()
Конечно, метод __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)
Объекты-экземпляры¶Instance Objects
Что можно делать с объектами экземпляров? Единственные операции, поддерживаемые объектами экземпляров, – это ссылки на атрибуты. Существует два вида допустимых имён атрибутов: атрибуты-данные и методы.
Атрибуты данных соответствуют «переменным экземпляра» в 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 и так далее. Однако в дальнейшем обсуждении мы будем использовать термин «метод» исключительно для обозначения методов экземпляров классов, если явно не указано иное.)
Допустимые имена методов объекта-экземпляра зависят от его класса. По определению, все атрибуты класса, являющиеся объектами-функциями, определяют соответствующие методы его экземпляров. Так, в нашем примере x.f является допустимой ссылкой на метод, поскольку MyClass.f – это функция, а x.i – нет, так как MyClass.i не является функцией. Но x.f – это не то же самое, что MyClass.f; это объект метода, а не объект функции.
Объекты методов¶Method Objects
Обычно метод вызывается сразу после его связывания:
x.f()
В примере с MyClass это вернёт строку 'hello world'. Однако не обязательно вызывать метод сразу: x.f – это объект метода, который можно сохранить и вызвать позже. Например:
xf = x.f
while True:
print(xf())
будет продолжать печатать hello world до скончания времён.
Что именно происходит при вызове метода? Вы, вероятно, заметили, что x.f() был вызван без аргумента выше, хотя определение функции f() задавало аргумент. Что случилось с аргументом? Разумеется, Python вызывает исключение, когда функцию, требующую аргумент, вызывают без аргументов – даже если аргумент фактически не используется...
На самом деле вы, возможно, догадались: особенность методов в том, что объект передаётся как первый аргумент функции. В нашем примере вызов x.f() в точности эквивалентен MyClass.f(x). В общем, вызов метода со списком из n аргументов эквивалентен вызову соответствующей функции со списком аргументов, который создаётся вставкой объекта метода перед первым аргументом.
Если вы всё ещё не понимаете, как работают методы, возможно, взгляд на реализацию прояснит ситуацию. Когда запрашивается атрибут экземпляра, не являющийся атрибутом-данными, выполняется поиск в его классе. Если имя обозначает допустимый атрибут класса, являющийся объектом-функцией, создаётся объект-метод путём упаковки (указателей на) объекта-экземпляра и только что найденного объекта-функции вместе в абстрактный объект: это и есть объект-метод. Когда объект-метод вызывается со списком аргументов, он снова распаковывается, создаётся новый список аргументов из объекта-экземпляра и исходного списка аргументов, и объект-функция вызывается с этим новым списком аргументов.
Разные замечания¶Random Remarks
Атрибуты данных переопределяют одноимённые атрибуты методов; чтобы избежать случайных конфликтов имён, которые могут вызвать труднонаходимые ошибки в больших программах, разумно использовать какое-либо соглашение, минимизирующее вероятность конфликтов. Возможные соглашения включают написание имён методов с заглавной буквы, добавление префикса к именам атрибутов данных в виде короткой уникальной строки (возможно, просто подчёркивания) или использование глаголов для методов и существительных для атрибутов данных.
Атрибуты данных могут использоваться как методами, так и обычными пользователями («клиентами») объекта. Иными словами, классы не пригодны для реализации чистых абстрактных типов данных. На самом деле в Python нет возможности принудительно скрыть данные – всё строится на соглашениях. (С другой стороны, реализация Python, написанная на C, может полностью скрыть детали реализации и контролировать доступ к объекту, если это необходимо; эта возможность может использоваться расширениями Python, написанными на C.)
Клиенты должны осторожно обращаться с атрибутами данных – они могут нарушить инварианты, поддерживаемые методами, если будут записывать свои значения в эти атрибуты. Обратите внимание, что клиенты могут добавлять собственные атрибуты данных к объекту экземпляра, не влияя на корректность методов, при условии отсутствия конфликтов имён – и снова соглашение об именах может избавить от многих проблем.
Не существует краткой формы для обращения к атрибутам данных (или другим методам!) из методов. Я нахожу, что это на самом деле повышает читаемость методов: при беглом просмотре метода невозможно перепутать локальные переменные и переменные экземпляра.
Часто первый аргумент метода называется self. Это не более чем соглашение: имя self не имеет для Python абсолютно никакого особого значения. (Однако обратите внимание, что если не следовать этому соглашению, ваш код может быть менее читаем для других программистов Python; кроме того, вполне возможно, что будет написана программа обозреватель классов, которая полагается на такое соглашение.)
Любой объект функции, являющийся атрибутом класса, определяет метод для экземпляров этого класса. Совершенно необязательно, чтобы определение функции было текстуально вложено в определение класса: можно также присвоить объект функции локальной переменной в классе. Пример:
# Функция, определённая вне класса
def f1(self, x, y):
return min(x, x+y)
class C:
f = f1
def g(self):
return 'hello world'
h = g
Теперь f, g и h – все являются атрибутами класса C, которые ссылаются на объекты-функции, и следовательно, все они являются методами экземпляров C – h в точности эквивалентно g. Заметьте, что такая практика обычно только сбивает с толку читателя программы.
Методы могут вызывать другие методы, используя атрибуты-методы аргумента 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)
Методы могут ссылаться на глобальные имена так же, как обычные функции. Глобальная область видимости, связанная с методом, – это модуль, содержащий определение класса. (Сам класс никогда не используется как глобальная область видимости!) Хотя редко встречаются веские причины для использования глобальных данных в методе, существует много законных применений глобальной области: например, функции и модули, импортированные в глобальную область, могут использоваться методами, а также функции и классы, определённые в ней. Обычно класс, содержащий метод, сам определён в этой глобальной области, и в следующем разделе мы найдём веские причины, по которым метод может захотеть сослаться на свой собственный класс!
Каждое значение – это объект, и поэтому у него есть класс (также называемый его типом). Он хранится как object.__class__.
Наследование¶Inheritance
Разумеется, языковая возможность не заслуживала бы названия «класс», если бы не поддерживала наследование. Синтаксис определения производного класса выглядит так:
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
Имя BaseClassName должно быть определено в области видимости, содержащей определение производного класса. Вместо имени базового класса также допускаются другие произвольные выражения. Это может быть полезно, например, когда базовый класс определён в другом модуле:
class DerivedClassName(modname.BaseClassName):
Выполнение определения производного класса происходит так же, как и для базового класса. При создании объекта класса базовый класс запоминается. Это используется для разрешения ссылок на атрибуты: если запрашиваемый атрибут не найден в классе, поиск продолжается в базовом классе. Это правило применяется рекурсивно, если сам базовый класс является производным от какого-либо другого класса.
В создании экземпляров производных классов нет ничего особенного: DerivedClassName() создаёт новый экземпляр класса. Ссылки на методы разрешаются следующим образом: ищется соответствующий атрибут класса, при необходимости спускаясь по цепочке базовых классов, и ссылка на метод считается допустимой, если в результате находится объект функции.
Производные классы могут переопределять методы своих базовых классов. Поскольку методы не имеют особых привилегий при вызове других методов того же объекта, метод базового класса, который вызывает другой метод, определённый в том же базовом классе, может в итоге вызвать метод производного класса, который его переопределяет. (Для программистов на C++: все методы в Python фактически виртуальные.)
Переопределяющий метод в производном классе на самом деле может захотеть расширить, а не просто заменить метод базового класса с тем же именем. Есть простой способ напрямую вызвать метод базового класса: просто вызовите BaseClassName.methodname(self, arguments). Иногда это полезно и для клиентов. (Заметьте, что это работает только в том случае, если базовый класс определён или импортирован непосредственно в глобальной области видимости.)
В Python есть две встроенные функции, работающие с наследованием:
- Используйте isinstance() для проверки типа объекта: isinstance(obj, int) будет True только если obj.__class__ является int или каким-то классом, производным от int.
- Используйте issubclass() для проверки наследования классов: issubclass(bool, int) возвращает True, поскольку bool является подклассом int. Однако issubclass(float, int) возвращает False, поскольку float не является подклассом int.
Множественное наследование¶Multiple Inheritance
Python также поддерживает форму множественного наследования. Определение класса с несколькими базовыми классами выглядит так:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
В большинстве случаев, в простейших ситуациях, поиск атрибутов, унаследованных от родительского класса, можно представить как обход в глубину, слева направо, без повторного поиска в одном и том же классе при пересечении иерархии. Таким образом, если атрибут не найден в DerivedClassName, он ищется в Base1, затем (рекурсивно) в базовых классах Base1, и если он не был найден там, он ищется в Base2, и так далее.
На самом деле всё немного сложнее: порядок разрешения методов динамически изменяется, чтобы поддерживать кооперативные вызовы super(). Этот подход известен в некоторых других языках с множественным наследованием как call-next-method и является более мощным, чем вызов super, встречающийся в языках с одиночным наследованием.
Динамическое упорядочивание необходимо, поскольку во всех случаях множественного наследования присутствуют ромбовидные отношения (где по крайней мере один из родительских классов может быть достигнут несколькими путями из самого нижнего класса). Например, все классы наследуют от object, поэтому любой случай множественного наследования предоставляет более одного пути к object. Чтобы базовые классы не были доступны более одного раза, динамический алгоритм линеаризует порядок поиска таким образом, чтобы сохранялся порядок слева направо, указанный в каждом классе, каждый родитель вызывался только один раз, и это монотонно (то есть класс может быть подклассом без изменения порядка приоритета его родителей). В совокупности эти свойства позволяют проектировать надежные и расширяемые классы с множественным наследованием. Подробнее см. http://www.python.org/download/releases/2.3/mro/.
Приватные переменные¶Private Variables
Существует ограниченная поддержка приватных идентификаторов класса. Любой идентификатор вида __spam (не менее двух ведущих подчёркиваний, не более одного завершающего) текстуально заменяется на _classname__spam, где classname – это имя текущего класса с удалёнными ведущими подчёркиваниями. Это искажение (mangling) выполняется без учёта синтаксической позиции идентификатора, поэтому его можно использовать для определения приватных переменных экземпляра и класса, методов, переменных, хранящихся в глобальных, и даже переменных, хранящихся в экземплярах, приватных для данного класса на экземплярах других классов. Усечение может произойти, если искажённое имя становится длиннее 255 символов. Вне классов или когда имя класса состоит только из подчёркиваний, искажения не происходит.
Искажение имён (name mangling) предназначено для того, чтобы дать классам простой способ определять «приватные» переменные и методы экземпляра, не беспокоясь о переменных экземпляра, определённых производными классами, или о вмешательстве в переменные экземпляра из кода вне класса. Обратите внимание, что правила искажения в основном разработаны для предотвращения случайностей; решительный человек всё же может получить доступ или изменить переменную, считающуюся приватной. Это может быть даже полезно в особых обстоятельствах, например в отладчике, и это одна из причин, по которой эта лазейка не закрыта. (Небольшой баг: наследование класса с тем же именем, что у базового, делает возможным использование приватных переменных базового класса.)
Обратите внимание, что код, переданный в exec() или eval(), не считает имя класса вызывающего класса текущим классом; это аналогично эффекту оператора global, чей эффект также ограничен кодом, который был скомпилирован вместе. То же ограничение применяется к getattr(), setattr() и delattr(), а также при прямом обращении к __dict__.
Разное¶Odds and Ends
Иногда полезно иметь тип данных, аналогичный паскалевской «записи» (record) или сишной «структуре» (struct), объединяющий несколько именованных элементов данных. Пустое определение класса прекрасно подойдет:
class Employee:
pass
john = Employee() # Создать пустую запись сотрудника
# Заполнить поля записи
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000
Фрагмент кода Python, который ожидает конкретный абстрактный тип данных, часто может получить вместо него класс, эмулирующий методы этого типа данных. Например, если есть функция, которая форматирует некоторые данные из файлового объекта, можно определить класс с методами read() и readline(), которые получают данные из строкового буфера, и передать его в качестве аргумента.
Объекты метода экземпляра также имеют атрибуты: m.__self__ – это объект экземпляра, которому принадлежит метод m(), а m.__func__ – это объект функции, соответствующий методу.
Исключения – тоже классы¶Exceptions Are Classes Too
Пользовательские исключения также определяются классами. С помощью этого механизма можно создавать расширяемые иерархии исключений.
Существует две допустимые (семантические) формы оператора raise:
raise Class
raise Instance
В первой форме Class должен быть экземпляром type или класса, производного от него. Первая форма – это сокращение для:
raise Class()
Класс в предложении except совместим с исключением, если он является тем же классом или его базовым классом (но не наоборот – предложение except, указывающее производный класс, не совместимо с базовым классом). Например, следующий код выведет B, C, D в таком порядке:
class B(Exception):
pass
class C(B):
pass
class D(C):
pass
for c in [B, C, D]:
try:
raise c()
except D:
print("D")
except C:
print("C")
except B:
print("B")
Обратите внимание, что если бы предложения except были переставлены (сначала except B), то было бы выведено B, B, B – срабатывает первое подходящее предложение except.
Когда выводится сообщение об ошибке для необработанного исключения, печатается имя класса исключения, затем двоеточие и пробел, и, наконец, экземпляр, преобразованный в строку с помощью встроенной функции str().
Итераторы¶Iterators
К настоящему моменту, вероятно, уже заметно, что большинство объектов-контейнеров можно перебирать с помощью оператора for:
for element in [1, 2, 3]:
print(element)
for element in (1, 2, 3):
print(element)
for key in {'one':1, 'two':2}:
print(key)
for char in "123":
print(char)
for line in open("myfile.txt"):
print(line)
Этот стиль доступа ясен, краток и удобен. Использование итераторов пронизывает и объединяет Python. Внутренне оператор for вызывает iter() у объекта-контейнера. Функция возвращает итератор объект, который определяет метод __next__(), который получает доступ к элементам в контейнере по одному. Когда больше не осталось элементов, __next__() возбуждает исключение StopIteration, которое сообщает циклу for, что нужно завершиться. Вы можете вызвать метод __next__() с помощью next() встроенной функции; этот пример показывает, как всё работает:
>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
next(it)
StopIteration
Поняв механизм работы протокола итератора, легко добавить поведение итератора в свои классы. Определите метод __iter__(), который возвращает объект с методом __next__(). Если класс определяет __next__(), то __iter__() может просто вернуть self:
class Reverse:
"Iterator for looping over a sequence backwards"
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
>>> for char in Reverse('spam'):
... print(char)
...
m
a
p
s
Генераторы¶Generators
Генераторы – это простой и мощный инструмент для создания итераторов. Они пишутся как обычные функции, но используют оператор yield, когда нужно вернуть данные. Каждый раз при вызове на нём next() генератор возобновляет работу с того места, где остановился (он запоминает все значения данных и то, какая инструкция была выполнена последней). Пример показывает, что генераторы могут быть тривиально простыми в создании:
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]
>>> for char in reverse('golf'):
... print(char)
...
f
l
o
g
Всё, что можно сделать с помощью генераторов, можно также сделать с помощью итераторов на основе классов, как описано в предыдущем разделе. Что делает генераторы такими компактными, так это то, что методы __iter__() и __next__() создаются автоматически.
Ещё одна ключевая особенность заключается в том, что локальные переменные и состояние выполнения автоматически сохраняются между вызовами. Это сделало функцию проще для написания и гораздо понятнее, чем подход с использованием переменных экземпляра, таких как self.index и self.data.
В дополнение к автоматическому созданию методов и сохранению состояния программы, при завершении генераторы автоматически возбуждают StopIteration. В совокупности эти функции позволяют легко создавать итераторы, не требуя больше усилий, чем написание обычной функции.
Выражения-генераторы¶Generator Expressions
Некоторые простые генераторы можно кратко записать в виде выражений, используя синтаксис похожий на списковые включения, но с круглыми скобками вместо квадратных. Такие выражения предназначены для ситуаций, когда генератор используется сразу же окружающей функцией. Генераторные выражения компактнее, но менее универсальны, чем полноценные определения генераторов, и обычно более экономичны по памяти, чем эквивалентные списковые включения.
Примеры:
>>> sum(i*i for i in range(10)) # сумма квадратов
285
>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec)) # скалярное произведение
260
>>> from math import pi, sin
>>> sine_table = {x: sin(x*pi/180) for x in range(0, 91)}
>>> unique_words = set(word for line in page for word in line.split())
>>> valedictorian = max((student.gpa, student.name) for student in graduates)
>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']
Сноски
| [1] | За исключением одного. Объекты модулей имеют секретный атрибут только для чтения, называемый __dict__, который возвращает словарь, используемый для реализации пространства имён модуля; имя __dict__ является атрибутом, а не глобальным именем. Очевидно, что использование этого нарушает абстракцию реализации пространства имён и должно быть ограничено такими вещами, как посмертные отладчики. |