Термінологічні зауваження

ред.

За браком загальноприйнятої термінології щодо класів, я час від часу використовуватиму терміни, запозичені із Smalltalk та C++. (Як на мене, то я б особисто надав перевагу термінології мови Modula-3, через те, що її обє'ктна семантика ближча до Пайтона, ніж семантика мови C++, але вважаю, що чули про цю мову набагато менше читачів).

Об'єкти зберігають свою індивідуальність, і численні назви (в численних контекстах) можуть бути прив'язані до одного і того ж об'єкта. В інших мовах це відомо як "псевдонімізація". На перший погляд ця риса не отримує належного визнання і може бути безпечно проігнорована при роботі з базовими незмінними типами (числами, рядками, кортежами). Але псевдонімізація має (навмисні!) наслідки в семантиці коду Пайтона при роботі зі змінними об'єктами, такими як списки, словники і більшість типів, що репрезентують зовнішні одиниці (файли, вікна тощо). Зазвичай це діє на користь програмі, тому що псевдоніми до певної міри поводяться як вказівники (пойтнери). Зокрема, передача об'єкта — доволі ефективна операція, тому що передається лише вказівник, і якщо функція змінює об'єкт, переданий аргументом, то зміна буде видима і в середовищі, що викликало цю функцію; це дозволяє уникнути подвійного механізму передачі аргументів, який існує в мові Pascal.

Контексти та іменні простори

ред.

Перед тим, як починати розповідь про класи, слід розповісти дещо про правила контексту в мові Пайтон. Визначення класів певним чином впливають на іменні простори і для того, щоб повністю розуміти, що відбувається, потрібно розуміти, як саме взаємодіють контексти та іменні простори. Знання цього матеріалу також корисне для будь-якого досвідченого програміста мови Пайтон.

Почнімо з деяких визначень.

Іменний простір — це проектування назв у об'єкти. Більшість іменних просторів імплементовані як словники мови Пайтон, що загалом зовсім непомітне (за винятком ефективності виконання програми), але це може змінитися в майбутньому. Приклади іменних просторів: вбудовані назви (функції, наприклад abs(), та вбудовані винятки); глобальні назви всередині модуля; локальні назви при виклику функції. В певному розумінні набір атрибутів об'єкта також утворює іменний простір. Необхідно знати, що назви із різних іменних просторів зовсім не пов'язані між собою; наприклад, два різних модулі можуть визначити функцію maximize без жодного ризику поплутання — при виклику потрібної функції користувачі модулів повинні префіксувати її назвою відповідного модуля.

Між іншим, слово атрибут тут стосується будь-якої назви, розташованої за крапкою — наприклад, у виразі z.real, real - це атрибут об'єкта z. Строго кажучи, посилання на назви всередині модуля — це атрибутивні посилання: у виразі modname.funcname, modname — назва об'єкта модуля і funcname - його атрибут. У цьому випадку відбувається пряме проектування між атрибутами модуля і визначеними в ньому глобальними назвами: вони всі поділяють один іменний простір! <footnote>За винятком одного. Об'єкти модулів мають секретний атрибут, що доступний лише для зчитування, який зветься __dict__, і який повертає словник, що використовується для імплементації іменного простору модуля; назва __dict__ — лише атрибут, який не є глобальною назвою. Очевидно його використання порушує абстракцію дизайну іменного простору і тому повинно обмежуватися лише такими речами як зневаджувач при пошуку помилок.</footnote>

Атрибути можуть бути призначені як для зчитування, так і для запису нових величин. Останнє стосується атрибутів модулів, для яких можна написати: "modname.the_answer = 42". Змінні атрибути можуть видалятися за допомогою твердження del. наприклад,"del modname.the_answer" видалить атрибут the_answer з об'єкта, на який посилається назва modname.

Іменні простори створюються в різний час і мають різну тривалість життя. Іменний простір вбудованих назв створюється під час завантаження інтерпретатора і ніколи не видаляється. Глобальний іменний простір модуля створюється під час зчитування його визначення і загалом існує до виходу інтерпретатора. Твердження, що виконуються інтерпретатором на найвищому рівні (зчитані зі скрипта чи введені у діалоговому режимі), вважаються частиною модуля __main__, і таким чином мають свій власний іменний простір. Вбудовані назви також живіть у окремому модулі, що зветься __builtin__.

Локальний іменний простір функції створюється під час її виклику і видаляється при поверненні нею результату або при зведенні нею винятку, необробленого у самій цій функції. (Власне термін "забування" краще відбивав би те, що відбувається насправді). І, звичайно, кожен рекурсивний виклик функції має свій власний іменний простір.

Контекст — це текстуальний регіон програми, написаної на Пайтоні, де іменний простір доступний напряму. "Доступний напряму" тут означає, що безпосереднє посилання на назву спричинить її пошук у цьому іменному просторі.

Хоча контексти визначаються статично, вони використовуються динамічно. У будь-який момент виконання програми існують принаймні три вкладених контексти з доступними напряму іменними просторами:

  1. Внутрішній (з якого починається пошук) містить в собі локальні назви.
  2. Іменний простір оточуючих функцій, де пошук розпочинається з найближчого оточуючого контексту.
  3. Середній контекст (де продовжується пошук) містить глобальні назви поточного модуля.
  4. Зовнішній контекст, де пошук завершується, — це іменний простір, що містить вбудовані назви.

Якщо назва задекларована глобально, то всі посилання і призначення відбуваються у середньому контексті, де зберігаються глобальні назви модуля. В інакшому випадку, всі змінні, що зустрічаються поза внутрішнім контекстом, призначені лише для зчитування.

Загалом локальний контекст посилається на локальні назви (текстуально) поточної функції. Поза функціями локальний контекст посилається на той же іменний простір, що й глобальний контекст: іменний простір модуля. Визначення класів додають іще один іменний простір до локального контексту.

Важливо усвідомити, що контексти визначаються текстуально: глобальний контекст функції, визначеної в модулі, є іменним простором цього модуля, незалежно від того, звідки чи за допомогою якого псевдоніма її викликано. З іншого боку, сам пошук назв відбувається динамічно, під час виконання, хоча мова еволюціонує до статичного вирішення назв під час "компіляції", отже не варто покладатися на динамічне вирішення назв! (Уже зараз локальні змінні вирішуються статично).

Особливістю Пайтона є те, що призначення завжди відбуваються у внутрішньому контексті. Призначення не копіюють дані, а лише прив'язують назви до об'єктів. Те саме стосується і видалення: твердження "del x" видаляє прив'язку до x із іменного простору локального контексту. Насправді всі операції, що визначають нові назви, використовують локальний контекст: зокрема імпортування та визначення функцій прив'язують назву модуля чи функції у локальному контексті. (Твердження global може використовуватися як індикатор того, що дана змінна існує у глобальному контексті).

Перший погляд на класи

ред.

Класи додають трохи нового синтаксису, три нових типи об'єктів та трохи нової семантики.

Синтаксис визначення класів

ред.

Найпростіша форма визначення класу виглядає так:

class ClassName:
    твердження-1;
    .
    .
    .
    твердження-N;

Визначення класів, подібно до визначень функцій (заданих через твердження def) , повинні виконатися перед тим, як вони матимуть якийсь ефект. (Визначення класу в принципі може розташовуватися у розгалуженні твердження if чи всередині функції).

На практиці твердження всередині класу здебільшого містять визначення функцій, але й інші твердження тут дозволяються також — ми повернемося до цього пізніше. Визначення функцій, розташованих всередині класів мають особливу форму списку аргументів, що диктується правилами виклику методів, але про це також йтиметься далі.

При вході у визначення класу створюється новий іменний простір, що використовується у якості локального контексту, таким чином всі призначення нових величин локальним змінним потрапляють у цей новий іменний простір. Зокрема визначення функцій прив'язують назву нової функції тут.

Після нормального виходу з визначення класу (при його закінченні) створюється класовий об'єкт, який фактично є оболонкою змісту іменного простору, створеного цим визначенням (про класові об'єкти йтиметься в наступному розділі). Після цього поновлюється початковий локальний контекст (той, що існував перед входом до визначення класу), у якому класовий об'єкт прив'язується до вказаної у заголовці визначення класу назви (у наведеному вище прикладі ClassName).

Класові об'єкти

ред.

Класові об'єкти підтримують два типи операцій: атрибутивні посилання та реалізацію.

Атрибутивні посилання використовують стандартний синтаксис атрибутивних посилань мови Пайтон у вигляді obj.name. Дійсні атрибутивні назви складаються з усіх назв, що існували в іменному просторі класу під час створення класового об'єкта. Тобто, якщо визначення класу виглядало таким чином:

class MyClass:
    "Простенький приклад класу"
    i = 12345
    def f(self):
        return 'привіт, світе'

то MyClass.i and MyClass.f є дійсними атрибутивними посиланнями, що повертають цілочислову величину та об'єкт методу відповідно. Атрибути класу можуть також набувати нових значень, тобто MyClass.i може змінюватися через призначення. __doc__ - також дійсний атрибут; він повертає рядок документації відповідного класу: "Простенький приклад класу".

Реалізація класу використовує нотацію функцій. Просто вдайте собі, що класовий об'єкт - це безпараметрова функція, яка повертає нову реалізацію класу. Відповідно до наведеного вище прикладу

x = MyClass()

створює нову реалізацію класу і призначає цей новостворений об'єкт локальній змінній x.

Операція реалізації ("виклик" класового об'єкта) створює пустий об'єкт. Часто класи створюють об'єкти із заданим початковим станом. Для цього клас може визначити спеціальний метод, що зветься __init__(), наприклад:

def __init__(self):
        self.data = []

Якщо в класі визначено метод __init__(), то він автоматично викликається при реалізації нового класу. Для наведеного вище прикладу нова ініціалізована реалізація класу може бути отримана так:

x = MyClass()

І звичайно, метод __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 &lt; 10:
    x.counter = x.counter * 2
print x.counter
del x.counter

Другий тип атрибутивного посилання, зрозумілий реалізованим об'єктам - це методи. Метод — це просто функція, що "належить" об'єкту. (У Пайтоні термін "метод" вживається не лише по відношенню до реалізацій класів: інші типи об'єктів можуть також мати методи. Зокрема, спискові об'єкти мають методи для додавання, видалення, вставки, сортування тощо. Однак у цьому розділі термін "метод" вживатиметься винятково для позначення об'єктів реалізованих класів, якщо не зазначено інакше).

Дійсні назви методів реалізованих об'єктів залежать від відповідного класу. За визначенням усі атрибути класу, які є (визначеними користувачем) об'єктами функцій, визначають відповідні методи реалізованих об'єктів. Зокрема, x.f — це дійсне посилання методу, тому що MyClass.f — функція, але x.i - ні, тому що MyClass.i не є функцією. Але x.f - не те саме, що й MyClass.f -- це об'єкт методу, а не функції.

Об'єкти методів

ред.

Зазвичай виклик методу відбувається відразу:

x.f()

Для нашого прикладу це поверне рядок 'привіт, світе'. Але виклик методу в момент посилання на нього не є обов'язковим: якщо x.f — об'єкт методу, то він може бути збережений і викликаний пізніше. Наприклад:

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

виводитиме "привіт, світе" до кінця світу.

Що ж насправді відбувається при виклику методу? Можливо ви помітили, що x.f() було викликано без зазначеного у визначенні аргументу. Що ж трапилося? Адже відомо, що Пайтон створює виняток, коли функція, яка потребує аргументу, викликається без нього, навіть якщо той аргумент насправді не використовується...

Можливо ви вже вгадали відповідь: особливою рисою методів є те, що відповідний об'єкт передається через перший аргумент функції. Для нашого прикладу x.f() повністю еквівалентне MyClass.f(x). Загалом виклик методу зі списком у n аргументів відповідає виклику відповідної функції зі списком, що створюється додаванням об'єкта методу перед першим аргументом.

Якщо вам ще не зовсім зрозуміло, як працюють методи, висвітлення окремих деталей імплементації може дещо прояснити стан речей. При посиланні на атрибут реалізації, що не є атрибутом даних, відбувається пошук у відповідному класі. Якщо назва позначає дійсний атрибут класу, що є функційним об'єктом, то створюється об'єкт методу шляхом запаковування (пойнтера) реалізованого об'єкта та об'єкта функції в єдиному абстрактному об'єкті, що і є об'єктом методу. При виклику об'єкта методу зі списком аргументів, він знову розпаковується і новий список аргументів створюється із реалізованого об'єкта і початкового списку аргументів, після чого об'єкт функції викликається з новим списком аргументів.

Класи

ред.

Випадкові зауваження

ред.

Атрибути даних перекривають однойменні атрибути методів. Щоб запобігти випадковим конфліктам назв, що можуть призвести до проблем, які буде досить важко віднайти у великих програмах, гарною ідеєю є використання певних конвенцій, що допоможуть мінімізувати ризик таких зіткнень. Такими конвенціями може бути вживання великої літери у назвах методів, префіксація назв атрибутів даних (можливо за допомогою лише нижньої риски), використання дієслів у назвах методів та іменників у атрибутах даних тощо.

Посилання на атрибути даних може робитися як методами так і звичайними користувачами ("клієнтами") об'єкта. Іншими словами, класи не придатні для імплементації чистих абстрактних типів даних. Більше того, у Пайтоні немає нічого, що уможливлює приховування даних — все базується лише на конвенціях. (З іншого боку, імплементація Пайтона, написана на C, може повністю приховати деталі і контролювати доступ до об'єктів якщо таке потрібно; це може використовуватися розширеннями Пайтона, написаними на 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 -- при чому 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)

Методи, так само як і звичайні функції, можуть посилатися на глобальні назви. Глобальний контекст, асоційований з методом, — це модуль, де знаходиться визначення класу. (Сам же клас ніколи не використовується як глобальний контекст!). Хоча використання глобальних даних всередині методу не часто може бути виправдане, існує багато легітимних мотивів для використання глобального контексту: перш за все, функції та модулі імпортовані в глобальний контекст можуть використовуватися методами. Часто клас, що містить в собі метод, сам визначений у цьому глобальному контексті, а в наступному розділі ми побачимо й інші мотиви, через які метод потребує послання на власний клас!

Спадковість

ред.

Звичайно, без спадковості ця риса мови не могла б називатися "класом". Синтаксис похідного класу виглядає так:

class DerivedClassName(BaseClassName): # ПохіднийКлас(КласОснова)
    твердження-1;
    .
    .
    .
    твердження-N;

Назва BaseClassName повинна бути визначена в контексті, що містить визначення похідного класу. Замість назви класу-основи можна вживати вираз. Це корисно, коли клас визначено в іншому модулі:

class DerivedClassName(modname.BaseClassName):

Виконання визначення похідного класу відбувається так само, як і для класу-основи. При конструкції класового об'єкта клас-основа запам'ятовується. Це використовується при вирішенні атрибутивних посилань: якщо потрібний атрибут не знайдено в класі, то пошук продовжується в класі-основі. Це правило застосовується рекурсивно, якщо клас-основа є похідним від якогось іншого класу.

Реалізація похідного класу не має в собі нічого особливого: DerivedClassName() створює нову реалізацію класу. Посилання на методи вирішуються таким чином: відповідний класовий атрибут шукається в похідному класі і, якщо необхідно, вниз по ланцюгу класів-основ; посилання на метод є дійсним, якщо знайдено відповідний функційний об'єкт.

Похідні класи можуть перевизначити методи своїх класів-основ. Оскільки методи не мають спеціальних привілеїв при виклику інших методів свого об'єкта, метод класу-основи, що викликає інший метод, визначений у тому ж самому класі-основі, може натомість викликати метод похідного класу, що перевизначає його. (Для програмістів мови C++ усі методи Пайтона можуть вважатися віртуальними (virtual).

Перевизначення методу похідного класу може використовуватися не стільки для заміни, як для розширення однойменного методу. Метод класу-основи дуже просто викликати напряму: "НазваКласуОснови.назваМетоду(посиланняНаДанийКласс, аргументи)". Інколи це корисно і для клієнтів. (Зауважте, що це працює лише тоді, коли клас-основа визначений в глобальному контексті чи напряму імпортується в нього).

Множинна спадковість

ред.

Пайтон підтримує обмежену форму множинної спадковості. Визначення класу з кількома класами-основами виглядає так:

class DerivedClassName(Base1, Base2, Base3): # ПохіднийКлас(Основа1, Основа2, Основа3):
    твердження-1
    .
    .
    .
    твердження-N

Єдине правило, що потребує пояснення — це вирішення атрибутивних посилань. Пошук відбувається в глибину зліва направо. Тобто, якщо атрибут не знайдено в похідному класі (DerivedClassName), пошук продовжується у класі Base1, а потім (рекурсивно) в класах-основах Base1. В разі, якщо його тут не знайдено, пошук продовжуватиметься у Base2 і т.д.

(Для декого пошук в ширину -- тобто пошук у Base2 та Base3 перед класами-основами Base1 -- виглядає більш природнім. Однак це потребувало б знання, чи певний атрибут класу Base1 визначений саме в ньому чи в одному з його класів-основ, перед тим як визначити наслідки конфліктів назв з атрибутами класу Base2. Пошук у глибину не розрізняє між прямими та успадкованими атрибутами Base1).

Зрозуміло, що беззастережне використання множинної спадковості може жахливо ускладнити утримання коду, особливо з огляду на те, що для уникнення конфліктів назв Пайтон покладається виключно на конвенції. Добре відома проблема множинної спадковості — коли клас походить із двох класів, які мають той самий клас-основу. Що при цьому відбувається — зрозуміти неважко (реалізація матиме єдину копію "реалізованих змінних" чи атрибутів даних, що використовуються спільним класом-основою), але не зрозуміло чи така семантика є дійсно корисною.

Приватні змінні

ред.

Існує обмежена підтримка для приватних ідентифікаторів класів. Будь-який ідентифікатор, що має форму __spam (мінімум дві нижні риски на початку і максимум одна в кінці) текстуально замінюється на _classname__spam, де classname - назва поточного класу, з якої було видалено початкові нижні риски. Це робиться безвідносно до синтаксичної позиції ідентифікатора, тобто він може використовуватися для визначення приватних реалізацій, класових змінних, методів та глобальних назв, навіть для зберігання реалізованих змінних, приватних для цього класу в реалізаціях інших класів. Скорочення може відбутися, якщо новоутворена назва довша за 255 символів. Поза класами (або якщо назва класу складається лише з нижніх рисок) жодних перетворень назви не відбувається.

Трансформація назв надає класам простий спосіб визначення "приватних" реалізованих змінних та методів без зайвих турбот щодо реалізованих змінних, визначених у похідних класах, чи зміни реалізованих змінних поза межами класу. Зауважте, що правила заміни назв створені насамперед для уникнення неприємних випадків; але при бажанні настирлива душа все одно зможе зчитати або змінити змінну, що вважається приватною. В окремих випадках (наприклад, при розробці зневаджувача) це навіть може бути корисним, що і є однією з причин чому ця дірочка ще й досі не зачинена. (Вада: при утворенні похідного класу з тією ж назвою, що й клас-основа стає можливим доступ для приватних змінних класу-основи).

Зауважте, при передачі коду через exec, eval() чи evalfile() назва викликаючого класу не вважається поточною. Це подібно до твердження global, ефект дії якого також обмежується кодом, що був доступним у момент компіляції у послідовність байтів. Це ж обмеження стосується і getattr(), setattr() та delattr(), а також прямого посиланні на __dict__.

Різне

ред.

Інколи корисно мати тип даних подібний до "record" мови Pascal' чи "struct мови C, що зліплює докупи кілька названих одиниць даних. Це чудово виходить за допомогою пустого визначення класу:

class Employee:
    pass

john = Employee() # Створити нового робітника

# І заповнити його відповідними даними
john.name = 'Іван Іванченко'
john.dept = 'комп. лабораторія'
john.salary = 1000

Якщо певний код очікує особливого абстрактного типу даних, то цьому коду можна звичайно передати клас, що натомість імітує методи того типу даних. Наприклад, якщо ви маєте функцію, що форматує певні дані, отримані із файлового об'єкта, ви можете визначити клас із методами read() та readline(), що замість цього отримує дані із рядкового буфера, і передати його у якості аргументу.

Об'єкти реалізованих методів також мають атрибути: m.im_self - об'єкт, метод якого є реалізацією, і m.im_func — об'єкт функції, що відповідає цьому методу.

Винятки теж класи

ред.

Визначені користувачем винятки — також класи. Використання цього механізму дозволяє створювати розширювані ієрархії класів.

Існують дві дійсні (семантичні) форми тверджень підняття винятків:

raise Class, instance # raise Клас, реалізація

raise instance # raise реалізація

У першій формі, instance повинна бути реалізацією класу Class або іншого класу, що походить від нього. Друга форма — це скорочення для:

raise instance.__class__, instance

Клас, розташований у твердженні 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 були розташовані у зворотному порядку (з "except B" на початку), то вивід виглядав би B, B, B — найперше твердження except відфільтрувало б усі винятки.

При виводі повідомлення про помилку зсередини непозначеного винятку, що є класом, спочатку виводиться назва класу, потім двокрапка і пробіл, і врешті-решт реалізація, переведена в рядок за допомогою вбудованої функції str().

Ітератори

ред.

Ви вже напевно помітили, що більшість складених об'єктів можуть вживатися у циклічних конструкціях та оброблятися за допомогою твердження 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

Такий стиль доступу до елементів є чистим, стислим і зручним. Використання ітераторів наповнює і об'єднує мову Пайтон. Поза кулісами твердження for викликає функцію iter() для складеного об'єкта, яка повертає об'єкт-ітератор, який в свою чергу має метод next(), що надає послідовний доступ до елементів складеного об'єкта. Коли елементи закінчуються, next() відкидає виняток StopIteration, який повідомляє про закінчення циклу. Цей приклад ілюструє, як все це відбувається:

>>> s = 'abc'
>>> it = iter(s)
>>> it
    iterator object at 0x00A1DB50
>>> it.next()
'a'
>>> it.next()
'b'
>>> it.next()
'c'
>>> it.next()

Traceback (most recent call last):
  File "pyshell#6", line 1, in -toplevel-
    it.next()
StopIteration

Завдяки цьому механізму досить легко додати властивості ітератора вашим класам. Для цього слід визначити метод __iter__(), що повертає об'єкт з методом next(). Якщо сам клас визначає метод next(), то __iter__() може просто повернути self:

>>> class Reverse: # клас ЗворотнийПорядок
    "Ітератор для обробки послідовності в зворотному порядку"
    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

Генератори

ред.

Генератори — це простий і потужний інструмент для створення ітераторів. Вони виглядають як звичайні функції, але використовують твердження 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. Все це разом взяте дозволяє створювати ітератори з тією ж простотою що і звичайні функції.

Генераторні вирази

ред.

Окремі прості генератори можна закодувати в короткій формі у вигляді виразів, використовуючи синтаксис, подібний до створення списків, але з напівкруглими дужками замість квадратних. Ці вирази призначені для ситуацій, коли генератори відразу використовуються оточуючою функцією. Генераторні вирази більш компактні, але менш універсальні порівняно з повними визначеннями генераторів, а також поводяться краще по відношенню до пам'яті комп'ютера аніж еквівалентні спискові конструкції.

Приклади:

>>> 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 = dict((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']