Информационные технологииStfw.Ru 🔍

Классы

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

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

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

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

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

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

Под словом атрибут мы подразумеваем любое имя, следующее после точки: например, в выражении z.real, real является атрибутом объекта z. Строго говоря, имена в модулях являются атрибутами модуля: в выражении modname.funcname, modname является объектом-модулем и funcname является его атрибутом. В этом случае имеет место прямое соответствие между атрибутами модуля и глобальными именами, определенными в модуле: они совместно используют одно пространство имен! [Есть одно исключение. Объект-модуль имеет секретный атрибут _dict_, содержащий словарь, используемый для реализации пространства имен модуля. Имя _dict_ является атрибутом, однако не является глобальным именем. Не следует использовать атрибут _dict_ где-либо кроме отладчиков, так как это нарушает абстракцию реализации пространства имен.]

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

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

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

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

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

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

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

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

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

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

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

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

class имя_класса: инструкция1 . . . инструкцияN

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

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

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

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

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

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

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

class MyClass: 'Простой пример класса' 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)


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

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

Первый - атрибуты данных. Они соответствуют "переменным экземпляра" в 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).

Действительные имена методов объекта-экземпляра зависят от класса: все атрибуты класса, являющиеся объектами-функциями автоматически становятся методами при обращении к соответствующим атрибутам экземпляра [Функция может быть записана в lambda-форме, однако другие объекты, поддерживающие вызов (класс или экземпляр класса, для которого определен метод _call_) не подходят.]. Так, в нашем примере x.f является методом, привязанным к объекту x. Но x.f - это не то же самое, что и MyClass.f. В первом случае метод "знает" объект, к которому он применяется, во втором - он не привязан к какому-либо объекту и ведет себя аналогично функции.

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

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

x.f()

В нашем примере он вернет строку 'Привет всему миру'. Однако совсем не обязательно вызывать метод прямо. x.f является объектом-методом, и его можно сохранить для дальнейшего использования:

xf = x.f
while 1: print xf()

будет выводить 'Привет всему миру' до тех пор, пока выполнение не будет прервано.

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

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

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

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


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

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

Обычно первый аргумент в определении метода называют 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)

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

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

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

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

class производный_класс(базовый_класс): инструкция1 . . . инструкцияN

Базовый класс (базовый_класс) должен быть определен в области видимости, в которой находится определение производного класса (производный_класс). Вместо имени базового класса можно использовать выражение, например, если базовый класс определен в другом модуле:

class производный_класс(модуль.базовый_класс):

Определение производного класса выполняется так же, как и определение базового класса. Сконструированный объект-класс помнит базовый - он используется для разрешения имен атрибутов: если запрошенный атрибут не найден в классе, поиск продолжается в базовом классе. Это правило применяется рекурсивно, если базовый класс, в свою очередь, является производным от другого класса. В создании экземпляра производного класса нет ничего особенного: производный_класс() порождает новый экземпляр класса.

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

Переопределяя метод в производном классе, Вы можете также захотеть вызвать метод базового класса с тем же именем. Это можно сделать напрямую: просто вызовите 'базовый_класс.метод(self, список_аргументов)', где базовый_класс - ссылка на базовый класс в том виде, в котором он доступен в текущей области видимости.

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

class производный_класс(базовый_класс1, базовый_класс2, базовый_класс3): инструкция1 . . . инструкцияN

Единственное правило, необходимое для объяснения семантики, - правило разрешения имен атрибутов. Поиск производится сначала в глубину, затем слева направо. Таким образом, если атрибут не найден в производный_класс, то он ищется сначала в базовый_класс1, затем (рекурсивно) в базовых классах класса базовый_класс1 и только потом в базовый_класс2 и т. д. (Для некоторых людей кажется более естественным другой порядок разрешения имен атрибутов - сначала в классах базовый_класс1, базовый_класс2, базовый_класс3 и только потом в базовых классах класса базовый_класс1. Однако в этом случае возникает зависимость результата от реализации каждого из базовых классов. С принятым же правилом, нет разницы между прямыми и унаследованными атрибутами базовых классов.)
9.6. Частные атрибуты

Python предоставляет ограниченную поддержку частных атрибутов классов. Любой атрибут вида _атрибут (имя которого содержит не менее двух символов подчеркивания в начале и не более одного в конце) заменяется на _класс_атрибут, где класс - имя текущего класса с "обрезанными" символами подчеркивания в начале. Такая обработка производится независимо от синтаксического расположения идентификатора, то есть может использоваться для определения частных атрибутов, доступ к которым будет возможен только из методов этого класса и методов его экземпляров. (Имя может быть обрезано, если его длина превысит 255 символов.) Если Вы ссылаетесь на имя, находясь за пределами класса, или если имя класса состоит только из символов подчеркивания, то оно преобразованию не подлежит.

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

Если Вы из класса вызываете код с помощью exec, execfile, eval() или evalfile(), то внутри этого кода класс не будет считаться текущим: ситуация аналогична использованию инструкции global - действие ограничивается единовременно байт-компилированным кодом. Это ограничение распространяется и на getattr(), setattr() и delattr(), а также на прямое использование _dict_.

Примеры использования классов

Иногда полезно иметь тип данных (record в Pascal или struct в C), объединяющий несколько именованных единиц данных. С этой задачей прекрасно справится пустой класс:

class Employee: pass

# Создаем пустую карточку на служащего
john = Employee()

# Запоняем поля карточки:
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000

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

Часто вместо ожидаемого типа данных в функции (методе) можно использовать экземпляр класса, эмулирующего методы этого типа. Например, если есть функция, считывающая данные из файла, Вы можете определить класс с методами read() и readline(), которые будут брать данные из буфера вместо файла, и передать его экземпляр функции в качестве аргумента. Используя же специальные методы (см. раздел 11.6.3), можно эмулировать поведение чисел, списков, словарей и даже полностью контролировать доступ к атрибутам.

В библиотеке стандартных модулей Вы найдете множество примеров классов, эмулирующих поведение строк, списков, словарей, файлов. Рекомендуем посмотреть на реализацию таких модулей, как UserString, UserList и UserDict, StringIO. Кроме того, в дистрибутив обычно входит несколько демонстрационных модулей, среди которых Вы найдете много интересных примеров, показывающих, как, например, можно реализовать рациональные числа.

Экземпляры классов в качестве исключений

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

Чаще всего инструкция raise употребляется в следующем виде:

raise экземпляр_класса

После ключевого слова except могут быть перечислены как строковые объекты, так и классы. Указанный класс считается "совместимым" с исключением, если исключение является экземпляром этого класса или класса, производного от него (но не наоборот - если в ветви except указан производный класс от того, экземпляром которого является исключение, то исключение не обрабатывается). Следующий пример выведет (именно в этом порядке) 'B', 'C', 'D':

class B: 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 в обратном порядке, то Вы получите 'B', 'B', 'B' - срабатывает первая ветвь except, совместимая с исключением.

Если исключение-экземпляр не обрабатывается, выводится сообщение об ошибке: имя класса, двоеточие, пробел и строка, полученная применением встроенной функции str() к исключению-экземпляру.

>>> class MyError:
... def _init_(self, value):
... self.value = value
... def _str_(self):
... return ‘self.value‘
...
>>> raise MyError(1)
Traceback (innermost last): File "<stdin>", line 1
_main_.MyError: 1

Язык имеет встроенный набор исключений, которые описаны в разделе 13. В качестве базового для всех исключений рекомендуется использовать класс Exception - это позволит полностью или частично избежать определения методов, необходимых для инициализации (метод _init_()) и вывода сообщения об ошибке (метод _str_(). В большинстве случаев определение нового исключения будет выглядеть совсем просто:

>>> class MyError(Exception): pass
...
>>> raise MyError('Oops!')
Traceback (most recent call last): File "<stdin>", line 1, in ?
_main_.MyError: Oops!


Классы-помощники

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

class parallel: def _init_(self, *args): self._args = args def _getitem_(self, item): return map(lambda s, i=item: s, self._args)

С таким классом-помощником задача параллельного перебора элементов сильно упрощается:

>>> seq1 = xrange(10)
>>> seq2 = [1, 2, 3, 5, 7]
>>> for x, y in parallel(seq1, seq2):
... print x, y
...
0 1
1 2
2 3
3 5
4 7

Как же наша псевдопоследовательность "узнает" о том, что элементы в одной из последовательностей закончились? Дело в том, что индикатором конца последовательности при использовании цикла for или средств функционального программирования в языке Python служит исключение IndexError. Исключение генерируется при первом использовании индекса, выходящего за пределы любой из последовательностей (в нашем примере seq1 и seq2). Так что нам достаточно не обрабатывать его и инструкция for будет считать, что закончилась наша псевдопоследовательность.

В версии 2.0 языка появилась новая встроенная функции zip(), предназначенная специально для параллельного перебора нескольких последовательностей. Ее поведение аналогично приведенному здесь классу parallel, с одним лишь отличием - функция zip() создает полноценный список, в который можно вносить изменения.

Множества

По своей природе, множества находятся скорее ближе к словарям, чем к спискам. Так же, как и словари обеспечивают уникальность ключей, множества гарантируют уникальность входящих в него элементов. С другой стороны, списки обеспечивают порядок следования элементов, что для множеств совсем необязательно. Таким образом, встроенный тип dictionary может служить хорошей базой для реализации множеств. Ниже приведено примерное определение класса, реализующего множество.
class set:
 def _init_(self, seq = ()): # Атрибут '_data' содержит словарь, ключами # которого являются элементы множества. Делать # атрибут частным ('_data') нежелательно, так # как в этом случае будет сложно работать с # производными от set классами. if isinstance(seq, set): # Если seq является экземпляром set или # производного от него класса, можно # воспользоваться "секретами" реализации self._data = seq._data.copy() else: # Иначе мы считаем seq произвольной # последовательностью self._data = {} for item in seq: self._data[item] = None
 def items(self): # Этот метод позволит перебирать элементы # множества: # for item in myset.items(): # ... return self._data.keys()
 def tuple_key(self): # Сами множества изменяемые и не могут # использоваться в качестве ключа в словаре или # элемента в другом множестве. Для этого их # нужно преобразовать в кортеж. items = self._data.keys() # Сортируя элементы, мы можем гарантировать, # что порядок следования элементов в двух # множествах всегда будет одинаковым. items.sort() return tuple(items)
 def add(self, item): # Добавление элемента реализуется добавлением в # словарь пустой записи с соответствующим # ключем. self._data[item] = None
 def remove(self, item): if self._data.has_key(item): del self._data[item] else: # Множество не содержит такого элемента raise ValueError("item doesn't exist")
 def copy(self): return set(self)
 def _repr_(self): return 'set('+`self.items()`+')'
 def _len_(self): # Количество элементов в множестве, вызывается # функцией len(). Также определяет истинность # множества. return len(self._data)
 def _contains_(self, item): # Операторы 'in' и 'not in'. return self._data.has_key(item)
 def _cmp_(self, other): # Все операции сравнения. return cmp(self._data, other._data)
 def _or_(self, other): # Оператор '|' (объединение). res = set(self) res._data.update(other._data) return res
 def _ior_(self, other): # Оператор '|='. self._data.update(other._data) return self
 def _and_(self, other): # Оператор '&' (пересечение). # Будем перебирать элементы того множества, # которое содержит меньше элементов. if len(other._data) < len(self._data): self, other = other, self res = set() for item in self._data.keys(): if other._data.has_key(item): res._data[item] = None return res
 def _iand_(self, other): # Оператор '&='. for item in self._data.keys(): if not other._data.has_key(item): del self._data[item] return self
 def _sub_(self, other): # Оператор '-' (элементы, которые содержатся в # первом множестве и не содержатся во втором). res = set() for item in self._data.keys(): if not other._data.has_key(item): res._data[item] = None return res
 def _isub_(self, other): # Оператор '-='. for item in other._data.keys(): if self._data.has_key(item): del self._data[item] return self
 # Если мы реализуем вычитание, то для симметрии # следует также реализовать и сложение # (объединение). _add_ = _or_ _iadd_ = _ior_
 def _xor_(self, other): # Оператор '^' (элементы, содержащиеся только в # одном из множеств). if len(other._data) < len(self._data): self, other = other, self res = set(other) for item in self._data.keys(): if res._data.has_key(item): del res._data[item] else: res._data[item] = None return res
 def _ixor_(self, other): # Оператор '^=' for item in other._data.keys(): if self._data.has_key(item): del self._data[item] else: self._data[item] = None return self


Контроль доступа к атрибутам

С помощью специальных методов _getattr_(), _setattr_() и _delattr_() Вы можете контролировать все обращения к атрибутам экземпляра. Приведем пример класса, реализующего собственные методы _getattr_() и _setattr_() и сохраняющего все атрибуты в частной переменной:

class VirtualAttributes: _vdict = None # Таким образом мы можем получить преобразованное # имя атрибута _vdict: _vdict_name = locals().keys()[0]
 def _init_(self): # мы не можем записать 'self._vdict = {}', # т.к. при этом произойдет обращение к методу # _setattr_ self._dict_[self._vdict_name] = {}
 def _getattr_(self, name): return self._vdict[name]
 def _setattr_(self, name, value): self._vdict[name] = value

Python   Теги:

Читать IT-новости в Telegram
Информационные технологии
Мы в соцсетях ✉