руководствоPython
August 5, 2020

Классы

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

Пару слов об именах и объектах

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

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

Прежде чем вводить классы, вы должны узнать кое-что о правилах контекста Python. Определения классов разыгрывают несколько хитростей с пространствами имен, и вам нужно знать, как работают области и пространства имен, чтобы полностью понять, что происходит. Кстати, знание этого предмета полезно для любого продвинутого программиста на Python.

Давайте начнем с некоторых определений.:

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

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

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

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

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

Хотя области действия определены статически, они используются динамически. В любой момент во время выполнения есть 3 или 4 вложенных области, пространства имен которых доступны напрямую:

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

Если имя объявлено глобальным, то все ссылки и присваивания идут непосредственно в среднюю область, содержащую глобальные имена модуля. Чтобы связать переменные, найденные за пределами самой внутренней области видимости, можно использовать оператор nonlocal; если не объявлено нелокальным, эти переменные доступны только для чтения (попытка записи в такую ​​переменную просто создаст новую локальную переменную в самой внутренней области, оставив внешнюю переменную с одинаковым именем неизменной).

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

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

Особенность Python заключается в том, что, если оператор «not global» или « nonlocalв силе», присваивания имен всегда идут в самую внутреннюю область. Назначения не копируют данные - они просто привязывают имена к объектам. То же самое верно и для удалений: оператор del x  удаляет привязку x из пространства имен, на которое ссылается локальная область. Фактически, все операции, которые вводят новые имена, используют локальную область: в частности, операторы import и определения функций связывают имя модуля или функции в локальной области.

Этот global оператор может использоваться для указания того, что конкретные переменные находятся в глобальной области видимости и должны быть восстановлены там; nonlocal заявление указывает на то, что конкретные переменные живут в рамках ограждающей и должен быть переопределен там.

Примеры областей и пространств имен

Это пример, демонстрирующий, как обращаться к различным областям и пространствам имен, а также как 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

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

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

Синтаксис объявление класса

Простейшая форма определения класса выглядит так:

class ClassName:
    <statement-1>
    .
    .
    .
    <statement-N>

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

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

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

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

Объекты классов

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

Ссылки атрибутов используется стандартный синтаксис , используемый для всех ссылок атрибутов в Python: obj.name. Допустимые имена атрибутов - это все имена, которые были в пространстве имен класса при создании объекта класса. Итак, если определение класса выглядело так:

class MyClass:
    """A simple example class"""
    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)

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

Теперь, что мы можем сделать с объектами экземпляра? Единственные операции, понятные объектам экземпляра, это ссылки на атрибуты. Существует два вида допустимых имен атрибутов: атрибуты данных и методы.

Атрибуты данных соответствуют «переменным экземпляра» в Smalltalk и «членам данных» в C ++. Атрибуты данных не должны быть объявлены; подобно локальным переменным, они возникают, когда им впервые назначаются. Например, если x это экземпляр MyClass, созданного выше, следующий фрагмент кода напечатает значение 16, не оставляя следа:

x.counter = 1
while x.counter < 10:
    x.counter = x.counter * 2
print(x.counter)
del x.counter

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

Допустимые имена методов объекта экземпляра зависят от его класса. По определению, все атрибуты класса, которые являются объектами функции, определяют соответствующие методы его экземпляров. Таким образом, в нашем примере x.f - это допустимая ссылка на метод, так как MyClass.f - это функция, но x.i нет. Но x.f это не то же самое, что MyClass.f- это объект метода , а не объект функции.

Объекты методов

Обычно метод вызывается сразу после его привязки:

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 аргументов эквивалентен вызову соответствующей функции со списком аргументов, который создается путем вставки объекта экземпляра метода перед первым аргументом.

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

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

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

class Dog:

    kind = 'canine'         # переменная класса, общая для всех экземпляров

    def __init__(self, name):
        self.name = name    # переменная экземпляра, уникальная для каждого экземпляра

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind                  # работает для всех собак
'canine'
>>> e.kind                  # работает для всех собак
'canine'
>>> d.name                  # уникальный для d
'Fido'
>>> e.name                  # уникальный для e
'Buddy'

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

class Dog:

    tricks = []             # ошибка, нельзя такое использовать

    def __init__(self, name):
        self.name = name

    def add_trick(self, trick):
        self.tricks.append(trick)

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks                # неожиданно для всех собак
['roll over', 'play dead']

Правильный дизайн класса должен использовать переменную экземпляра:

class Dog:

    def __init__(self, name):
        self.name = name
        self.tricks = []    # создаем пустой список для каждой собаки
    def add_trick(self, trick):
        self.tricks.append(trick)

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks
['roll over']
>>> e.tricks
['play dead']

Случайные замечания

Если одно и то же имя атрибута встречается как в экземпляре, так и в классе, то поиск атрибута определяет приоритет экземпляра:

>>> class Warehouse:
        purpose = 'storage'
        region = 'west'

>>> w1 = Warehouse()
>>> print(w1.purpose, w1.region)
storage west
>>> w2 = Warehouse()
>>> w2.region = 'east'
>>> print(w2.purpose, w2.region)
storage east

На атрибуты данных могут ссылаться как методы, так и обычные пользователи («клиенты») объекта. Другими словами, классы не могут использоваться для реализации чисто абстрактных типов данных. Фактически, ничто в Python не позволяет принудительно скрывать данные - все это основано на соглашении. (С другой стороны, реализация Python, написанная на C, может полностью скрыть детали реализации и при необходимости контролировать доступ к объекту; это может использоваться расширениями Python, написанными на C.)

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

Здесь нет сокращения для ссылки на атрибуты данных (или другие методы!) Из методов. Мы обнаружили, что это на самом деле повышает удобочитаемость методов: нет возможности путать локальные переменные и переменные экземпляра при просмотре метода.

Часто первый аргумент метода вызывается self. Это не более чем соглашение: имя self имеет абсолютно никакого особого значения для Python. Тем не менее, обратите внимание, что при несоблюдении соглашения ваш код может быть менее читаемым для других программистов Python, и также возможно, что может быть написана программа для просмотра классов , основанная на таком соглашении.

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

# Function defined outside the class
def f1(self, x, y):
    return min(x, x+y)

class C:
    f = f1

    def g(self):
        return 'hello world'

    h = g

Теперь fg и h все атрибуты класса , Cкоторые относятся к объектам функций и , следовательно , все они являются методами экземпляров Chбудучи точно эквивалентны 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__.

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

Конечно, языковая функция не заслуживает названия «класс» без поддержки наследования. Синтаксис определения производного класса выглядит следующим образом:

class DerivedClassName(BaseClassName):
    <statement-1>
    .
    .
    .
    <statement-N>

Имя BaseClassName должно быть определено в области, содержащей определение производного класса. Вместо имени базового класса также разрешены другие произвольные выражения. Это может быть полезно, например, когда базовый класс определен в другом модуле:

class DerivedClassName(modname.BaseClassName):

Выполнение определения производного класса происходит так же, как и для базового класса. Когда создается объект класса, запоминается базовый класс. Это используется для разрешения ссылок на атрибуты: если запрошенный атрибут не найден в классе, поиск продолжается в базовом классе. Это правило применяется рекурсивно, если сам базовый класс является производным от другого класса.

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

Производные классы могут переопределять методы своих базовых классов. Поскольку методы не имеют особых привилегий при вызове других методов того же объекта, метод базового класса, который вызывает другой метод, определенный в том же базовом классе, может в конечном итоге вызвать метод производного класса, который его переопределяет. (Для программистов на C ++: все методы в Python эффективны virtual.)

Переопределяющий метод в производном классе может фактически захотеть расширить, а не просто заменить одноименный метод базового класса. Существует простой способ вызвать метод базового класса напрямую: просто вызовите BaseClassName.methodname (self, arguments). Иногда это бывает полезно и для клиентов. (Обратите внимание, что это работает, только если базовый класс доступен как BaseClassName в глобальной области.)

В Python есть две встроенные функции, которые работают с наследованием:

  • Переопределяющий метод в производном классе может фактически захотеть расширить, а не просто заменить одноименный метод базового класса. Существует простой способ вызвать метод базового класса напрямую: просто вызовите BaseClassName.methodname (self, arguments). Иногда это бывает полезно и для клиентов. (Обратите внимание, что это работает, только если базовый класс доступен как BaseClassName в глобальной области.)
  • Используйте issubclass () для проверки наследования класса: issubclass (bool, int) имеет значение True, поскольку bool является подклассом int. Однако issubclass (float, int) имеет значение False, поскольку float не является подклассом int.

Множественное наследование

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

class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>

Для большинства целей, в простейших случаях, вы можете рассматривать поиск атрибутов, унаследованных от родительского класса, как поиск в глубину слева направо, а не поиск дважды в том же классе, где есть перекрытие в иерархии. Таким образом, если атрибут не найден в DerivedClassName, он ищется в Base1, затем (рекурсивно) в базовых классах Base1, и, если он не был найден там, его искали в Base2, и так далее.

На самом деле, это немного сложнее; порядок разрешения методов изменяется динамически для поддержки совместных вызовов super(). Этот подход известен в некоторых других языках множественного наследования как call-next-method и является более мощным, чем супервызов, найденный в языках одиночного наследования.

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

Приватная переменная

«Частные» переменные экземпляра, к которым нельзя получить доступ, кроме как изнутри объекта, не существуют в Python. Однако существует соглашение, которому следует большая часть кода Python: имя с префиксом подчеркивания (например, _spam) должно рассматриваться как закрытая часть API (будь то функция, метод или член данных). Это следует рассматривать как деталь реализации и может быть изменено без предварительного уведомления.

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

Упорядочивание имен полезно для того, чтобы позволить подклассам переопределять методы без прерывания вызовов методов внутри класса. Например:

class Mapping:
    def __init__(self, iterable):
        self.items_list = []
        self.__update(iterable)

    def update(self, iterable):
        for item in iterable:
            self.items_list.append(item)

    __update = update   # private copy of original update() method

class MappingSubclass(Mapping):

    def update(self, keys, values):
        # provides new signature for update()
        # but does not break __init__()
        for item in zip(keys, values):
            self.items_list.append(item)

Приведенный выше пример будет работать, даже если MappingSubclassбудет введен __updateидентификатор, поскольку он заменяется _Mapping__updateна в Mappingклассе и _MappingSubclass__updateв MappingSubclass классе соответственно.

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

Обратите внимание, что код, переданный exec()или eval()не считающий имя класса вызывающего класса текущим классом; это похоже на действие globalоператора, действие которого аналогично ограничено кодом, который компилируется вместе побайтно. Же ограничение относится и к getattr()setattr()и delattr(), как и при обращении __dict__напрямую.

Запись значений вне класса

Иногда полезно иметь тип данных, подобный «записи» Pascal или «структуре» C, объединяющий вместе несколько именованных элементов данных. Подойдет определение пустого класса:

class Employee:
    pass

john = Employee()  # Create an empty employee record

# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000

Кусок кода Python, который ожидает определенный абстрактный тип данных, часто может быть передан классу, который вместо этого имитирует методы этого типа данных. Например, если у вас есть функция , которая форматирует некоторые данные из файлового объекта, вы можете определить класс с методами read()и readline()что получить данные из строки буфер вместо этого, и передать его в качестве аргумента.

У объектов метода экземпляра также есть атрибуты: m.__self__это объект экземпляра с методом m()и m.__func__объект функции, соответствующий методу.

Итераторы

К настоящему времени вы, вероятно, заметили, что большинство контейнерных объектов можно перебрать с помощью 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, end='')

Этот стиль доступа ясен, лаконичен и удобен. Использование итераторов пронизывает и унифицирует 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 <module>
    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]
>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
...     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))                 # sum of squares
285

>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec))         # dot product
260

>>> 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']

Заключение

Пост создан для тг-канала @coolcoders