Документация Python неофициальный перевод
Содержание страницы

9. КлассыClasses

Классы предоставляют способ объединения данных и функциональности. Создание нового класса создаёт новый тип объекта, позволяя создавать новые экземпляры этого типа. Каждый экземпляр класса может иметь атрибуты для поддержания своего состояния. Экземпляры класса также могут иметь методы (определяемые его классом) для изменения этого состояния.

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

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

(Из-за отсутствия общепринятой терминологии для описания классов, я буду иногда использовать термины Smalltalk и C++. Я бы использовал термины Modula-3, поскольку его объектно-ориентированная семантика ближе к Python, чем к C++, но, полагаю, немногие читатели слышали о нём.)

9.1. Несколько слов об именах и объектахA Word About Names and Objects

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

9.2. Области видимости и пространства имён PythonPython Scopes and Namespaces

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

Начнём с некоторых определений.

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

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

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

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

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

Область видимости – это текстовая область программы на Python, в которой пространство имён доступно напрямую. «Напрямую доступно» означает, что неквалифицированная ссылка на имя пытается найти это имя в пространстве имён.

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

  • самая внутренняя область, которая просматривается первой, содержит локальные имена

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

  • предпоследняя область содержит глобальные имена текущего модуля

  • самая внешняя область (просматривается последней) – это пространство имён, содержащее встроенные имена

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

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

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

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

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

9.2.1. Пример областей видимости и пространств имёнScopes and Namespaces Example

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

def scope_test():
    def do_local():
        spam = "local spam"

    def do_nonlocal():
        nonlocal spam
        spam = "nonlocal spam"

    def do_global():
        global spam
        spam = "global spam"

    spam = "test spam"
    do_local()
    print("After local assignment:", spam)
    do_nonlocal()
    print("After nonlocal assignment:", spam)
    do_global()
    print("After global assignment:", spam)

scope_test()
print("In global scope:", spam)

Вывод примера кода:

After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam

Обратите внимание, что присваивание локальной переменной (по умолчанию) не изменило привязку spam в scope_test. Присваивание nonlocal изменило привязку spam в scope_test, а присваивание global изменило привязку на уровне модуля.

Также видно, что не было предыдущей привязки для spam до присваивания global.

9.3. Первое знакомство с классамиA First Look at Classes

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

9.3.1. Синтаксис определения классаClass Definition Syntax

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

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

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

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

При входе в определение класса создаётся новое пространство имён, которое используется как локальная область видимости – таким образом, все присваивания локальным переменным попадают в это новое пространство имён. В частности, определения функций связывают имя новой функции здесь.

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

9.3.2. Объекты классовClass Objects

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

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

class MyClass:
    """Простой пример класса"""
    i = 12345

    def f(self):
        return 'hello world'

тогда MyClass.i и MyClass.f являются допустимыми обращениями к атрибутам, возвращающими целое число и объект функции соответственно. Атрибутам класса также можно присваивать, так что вы можете изменить значение MyClass.i присваиванием. __doc__ также является допустимым атрибутом, возвращающим строку документации, принадлежащую классу: "A simple example class".

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

x = MyClass()

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

Операция создания экземпляра («вызов» объекта класса) создаёт пустой объект. Многие классы предпочитают создавать объекты с экземплярами, настроенными на определённое начальное состояние. Поэтому класс может определить специальный метод с именем __init__(), например так:

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

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

x = MyClass()

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

>>> class Complex:
...     def __init__(self, realpart, imagpart):
...         self.r = realpart
...         self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)

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

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

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

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

Другой вид обращения к атрибуту экземпляра – это метод. Метод – это функция, которая «принадлежит» объекту.

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

9.3.4. Объекты методовMethod Objects

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

x.f()

Если x = MyClass(), как выше, это вернёт строку 'hello world'. Однако необязательно вызывать метод сразу: x.f – это объект метода, и его можно сохранить и вызвать позже. Например:

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

будет продолжать печатать hello world до бесконечности.

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

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

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

9.3.5. Переменные класса и экземпляраClass and Instance Variables

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

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'

Как уже говорилось в нескольких слов об именах и объектах, общие данные могут приводить к неожиданным последствиям в случае изменяемых объектов, таких как списки и словари. Например, список tricks в следующем коде не следует использовать в качестве переменной класса, поскольку единственный список будет общим для всех экземпляров 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']

9.4. Разные замечанияRandom Remarks

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

>>> 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; также возможно, что будет написана программа обозреватель классов, которая полагается на такое соглашение.

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

# Функция, определённая вне класса
def f1(self, x, y):
    return min(x, x+y)

class C:
    f = f1

    def g(self):
        return 'hello world'

    h = g

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

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

Разумеется, языковая возможность не заслуживала бы названия «класс», если бы не поддерживала наследование. Синтаксис определения производного класса выглядит так:

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

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

class DerivedClassName(modname.BaseClassName):

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

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

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

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

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

  • Используйте isinstance() для проверки типа экземпляра: isinstance(obj, int) будет True только если obj.__class__ является int или какого-либо класса, производного от int.

  • Используйте issubclass() для проверки наследования классов: issubclass(bool, int) является True, поскольку bool является подклассом int. Однако issubclass(float, int) является False, поскольку float не является подклассом int.

9.5.1. Множественное наследованиеMultiple Inheritance

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

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

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

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

Динамическое упорядочивание необходимо, потому что все случаи множественного наследования содержат одно или несколько ромбовидных отношений (когда по крайней мере один из родительских классов может быть достигнут несколькими путями от самого нижнего класса). Например, все классы наследуют от object, поэтому любой случай множественного наследования предоставляет более одного пути к object. Чтобы базовые классы не обрабатывались более одного раза, динамический алгоритм линеаризует порядок поиска, сохраняя указанный в каждом классе порядок слева направо, вызывая каждый родительский класс только один раз, и является монотонным (то есть класс может быть унаследован без влияния на порядок приоритета его родителей). В совокупности эти свойства позволяют проектировать надёжные и расширяемые классы с множественным наследованием. За подробностями обратитесь к The Python 2.3 Method Resolution Order.

В некоторых случаях множественное наследование не допускается; см. Множественное наследование для подробностей.

9.6. Приватные переменныеPrivate Variables

«Приватных» переменных экземпляра, к которым нельзя обратиться иначе как изнутри объекта, в 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   # приватная копия исходного метода update()

class MappingSubclass(Mapping):

    def update(self, keys, values):
        # предоставляет новую сигнатуру для update()
        # но не нарушает __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__.

9.7. РазноеOdds and Ends

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

from dataclasses import dataclass

@dataclass
class Employee:
    name: str
    dept: str
    salary: int
>>> john = Employee('john', 'computer lab', 1000)
>>> john.dept
'computer lab'
>>> john.salary
1000

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

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

9.8. ИтераторыIterators

К настоящему моменту вы, вероятно, заметили, что большинство объектов-контейнеров можно перебирать с помощью оператора for:

for element in [1, 2, 3]:
    print(element)
for element in (1, 2, 3):
    print(element)
for key in {'one':1, 'two':2}:
    print(key)
for char in "123":
    print(char)
for line in open("myfile.txt"):
    print(line, end='')

Такой стиль доступа ясен, краток и удобен. Использование итераторов пронизывает Python и объединяет его. За кулисами оператор for вызывает iter() у объекта-контейнера. Функция возвращает объект-итератор, который определяет метод __next__(), обращающийся к элементам контейнера по одному. Когда элементов больше нет, __next__() возбуждает исключение StopIteration, которое говорит циклу for завершиться. Вызвать метод __next__() можно с помощью встроенной функции next(); этот пример показывает, как всё это работает:

>>> s = 'abc'
>>> it = iter(s)
>>> it
<str_iterator object at 0x10c90e650>
>>> 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:
    """Итератор для перебора последовательности в обратном порядке."""
    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

9.9. ГенераторыGenerators

Генераторы – это простой и мощный инструмент для создания итераторов. Они пишутся как обычные функции, но используют оператор yield всякий раз, когда нужно вернуть данные. Каждый раз при вызове next() генератор возобновляет работу с того места, где остановился (он запоминает все значения данных и то, какой оператор был выполнен последним). Пример показывает, что создавать генераторы может быть тривиально просто:

def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]
>>> for char in reverse('golf'):
...     print(char)
...
f
l
o
g

Всё, что можно сделать с генераторами, можно сделать и с итераторами на основе классов, как описано в предыдущем разделе. Что делает генераторы такими компактными – это то, что методы __iter__() и __next__() создаются автоматически.

Ещё одна ключевая особенность: локальные переменные и состояние выполнения автоматически сохраняются между вызовами. Это делает функцию проще для написания и гораздо понятнее, чем подход с использованием переменных экземпляра, таких как self.index и self.data.

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

9.10. Генераторные выраженияGenerator Expressions

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

Примеры:

>>> sum(i*i for i in range(10))                 # сумма квадратов
285

>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec))         # скалярное произведение
260

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

>>> x = [[1,2,3], [], [4, 5]]
>>> g = (*i for i in x)
>>> list(g)
[1, 2, 3, 4, 5]

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

>>> f(x for x in y)
>>> f((x for x in y))

Как и следующие:

>>> f(*x for x in y)
>>> f((*x for x in y))

Сноски