Как вызвать класс в python
Перейти к содержимому

Как вызвать класс в python

  • автор:

Знакомство с объектно-ориентированным программированием в Python: классы и экземпляры

Новичок в ООП? Узнай, как создавать классы и экземпляры в Python

Olga Sayfudinova

NOP::Nuances of Programming

NOP::Nuances of Programming

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

Многие специалисты могут работать годами без необходимости писать код по принципам ООП.

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

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

Если вы хотите поучаствовать в проектах в области науки о данных с открытым кодом, то, скорее всего, вам придется работать с объектами. Тогда и понадобятся знания ООП!

Шаг 1: создаем свой первый класс в Python

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

Изучение ООП начинается с понимания классов. Вот общепринятое определение: классы — это «заготовки для создания объектов». Класс — это логическая структуризация данных и методов (методы равносильны функциям).

Зачастую классы основываются на объектах, которые окружают нас в деловой среде: клиенты, товары, сотрудники.

В сфере финансовых услуг мы храним и работаем с информацией о клиентах. Поэтому давайте создадим простой класс под названием «customer»:

В ООП у нас есть ключевое слово class (специалисты знакомы с функциональным эквивалентом этого слова — def).

def используется для определения функции (или метода, если функция находится внутри класса). По аналогии class используется для определения класса.

При определении класса customer мы не создаем в нем никаких клиентов, а делаем заготовку для создания клиентов-объектов.

Первое, что вы заметите внутри класса customer — это метод __init__. Метод __init__ является особым методом для классов в Python. Он вызывается каждый раз при создании класса.

__init__ — это конструктор класса.

Каждый раз при создании нового класса вы вызываете метод __init__ и используете аргумент self.

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

Параметр self представляет экземпляр объекта.

Как только мы определили метод __init__ и указали параметр self, начинаем перечислять остальные параметры/аргументы, которые используются в методе __init__. В нашем примере это first, last, mobile и monthly.

Так мы создали наш первый класс customer. Теперь добавим экземпляры этого класса.

Шаг 2: экземпляры и методы

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

Создание экземпляра

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

  • cust1 — экземпляр;
  • customer — класс.

Создание метода

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

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

Чтобы посмотреть, что происходит с методом annual, напишем следующее:

Полезный совет: для вызова атрибута в Python (например, fullname в классе customer) напишите следующее:

При вызове метода (а не атрибута) он заключается в скобки. См. выше, как это делалось с annual.

Вы создали новый класс customer, экземпляр cust1 и метод annual. Это первые практические блоки по написанию ООП-кода на Python!

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

12. Классы и объекты¶

12.1. Объектно-ориентированное программирование¶

Python является объектно-ориентированным языком программирования, что означает наличие в языке средств объектно-ориентированного программирования (ООП).

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

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

12.2. Определяемые пользователем типы данных¶

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

Рассмотрим понятие математической точки. В пространстве двух измерений, точка — это два числа (координаты), с которыми работают как с одним объектом. В математике координаты точки часто записываются в скобках, разделенные запятой. Например, (0, 0) представляет начало координат, а (x, y) представляет точку, расположенную на x единиц правее и на y единиц выше, чем начало координат.

Естественный способ представления точки на языке Python — с помощью двух чисел. Но остается вопрос: как именно объединить эти два числа в один составной объект? Очевидное и быстрое решение состоит в том, чтобы использовать список или кортеж, и в некоторых случаях оно будет наилучшим.

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

Определение нашего класса Point (англ.: точка) выглядит так:

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

Приведенное выше определение создает новый класс Point . Предложение pass ничего не делает; мы воспользовались им потому, что тело составного предложения не может быть пустым.

Для этой цели подойдет и документирующая строка:

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

Переменная p содержит ссылку на новый объект типа Point .

Можно думать о классе, как о фабрике по изготовлению объектов. Тогда наш класс Point — фабрика по изготовлению точек. Сам класс не является точкой, но содержит все, что необходимо для производства точек.

12.3. Атрибуты¶

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

Можно добавить новые элементы-данные к экземпляру класса с помощью точечной нотации:

Этот синтаксис подобен синтаксису для обращения к переменной или функции модуля, например, math.pi или string.uppercase . И модули, и экземпляры класса создают свое собственное пространство имен, и синтаксис для доступа к элементам тех и других — атрибутам — один и тот же. В данном случае атрибуты, к которым мы обращаемся, — элементы-данные в экземпляре класса.

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

диаграмма состояний Point

Переменная p ссылается на объект класса Point , который содержит два атрибута. Каждый из атрибутов ссылается на число.

Тот же самый синтаксис используется для получения значений атрибутов:

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

Точечную нотацию можно использовать как часть любого выражения, так что следующие предложения совершенно типичны:

Первая строка выводит (3, 4) . Вторая строка вычисляет значение 25.

12.4. Инициализирующий метод и self

Поскольку наш класс Point предназначен для представления математических точек в двумерном пространстве, все экземпляры этого класса должны иметь атрибуты x и y . Но пока это не так для наших объектов Point .

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

Метод ведет себя как функция, но является частью объекта. Доступ к методу, как и доступ к атрибутам-данным, осуществляется при помощи точечной нотации. Инициализирующий метод вызывается автоматически, когда вызывается класс.

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

Создадим несколько экземпляров точек, посмотрим на их атрибуты, и вызовем наш новый метод для этих объектов:

В определении метода первый параметр всегда указывает на экземпляр класса. Традиционно этому параметру дают имя self. В только что рассмотренном примере параметр self последовательно указывает на объекты p , q , и r .

12.5. Объекты как параметры¶

Объект можно передать в качестве параметра, как любое другое значение. Например:

Функция print_point принимает объект Point в качестве аргумента и выводит его значение. Если выполнить print_point(p) с объектом p , определенным выше, то функция выведет (3, 4) .

12.6. Равенство объектов¶

Смысл слова ‘равенство’ кажется совершенно ясным. Но если говорить об объектах, то мы скоро обнаружим неоднозначность этого слова.

Например, что означает утверждение, что значения двух переменных типа Point равны? Что соответствующие объекты Point содержат одинаковые данные (координаты точки)? Или что обе переменные указывают на один и тот же объект?

Чтобы выяснить, ссылаются ли две переменные на один и тот же объект, используется оператор == . Например:

Хотя p1 и p2 содержат одинаковые координаты, они являются разными объектами. Но если присвоить переменной p1 значение p2 , то две переменных будут альтернативными именами одного и того же объекта:

Этот тип равенства называется поверхностным равенством, потому что он сравнивает только ссылки, а не содержимое объектов.

Для того, чтобы сравнить содержимое объектов — проверить глубокое равенство — можно написать функцию, подобную этой:

Теперь, если создать два разных объекта, содержащих одинаковые данные, с помощью same_point можно выяснить, представляют ли они одну и ту же математическую точку.

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

12.7. Прямоугольники¶

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

Есть несколько вариантов. Мы могли бы указать координаты центра прямоугольника и его размер (ширину и высоту). Или указать координаты одного из углов и размер прямоугольника. Или указать координаты двух противоположных углов. Традиционный способ таков: указать левый верхний угол прямоугольника и его размер.

Определим новый класс Rectangle (англ.: прямоугольник):

И создадим экземпляр этого класса:

Этот код создает новый объект Rectangle с двумя атрибутами — числами с плавающей точкой – width (англ.: ширина) и height (англ.: высота). А для того, чтобы указать левый верхний угол, можно вставить объект внутрь объекта!

Операторы точка можно сочетать, как видно из этого примера. Выражение box.corner.x означает: возьмите объект, на который указывает box , получите его атрибут corner ; затем возьмите объект, на который указывает этот атрибут, и получите атрибут x этого последнего объекта.

Следующий рисунок иллюстрирует, что у нас получилось:

объект Rectangle

12.8. Объекты как возвращаемые значения¶

Функции могут возвращать объекты. Например, функция find_center берет Rectangle в качестве аргумента и возвращает Point с координатами центра прямоугольника:

Следующий код демонстрирует использование функции:

12.9. Объекты изменяемы¶

Состояние объекта изменяется путем присваивания значений его атрибутам. Например, чтобы изменить размер прямоугольника без изменения его местоположения, изменим значения width и height :

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

12.10. Копирование¶

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

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

После импортирования модуля copy , с помощью функции copy мы создаем новый объект класса Point . Объекты p1 и p2 являются разными объектами, но содержат одинаковые данные.

Для копирования простых объектов вроде Point , которые не содержат вложенных объектов, функции copy достаточно. Такое копирование называется поверхностным копированием.

Для объектов, подобных объектам Rectangle , которые содержат ссылку на объект Point , функция copy не совсем то, что обычно требуется. Она скопирует ссылку на объект Point , так, что и старый объект Rectangle , и новый, будут ссылаться на один и тот же объект Point .

Если мы создадим прямоугольник b1 и сделаем его копию b2 с помощью copy , то результат будет таким:

прямоугольники

Это, скорее всего, не то, что мы хотели получить. В этом случае вызов функции grow_rect с одним объектом Rectangle не повлияет на другой, однако, вызов move_rect (см. упражнения в конце главы) с любым из прямоугольников отразится на обоих! Такое поведение сбивает с толку и чревато ошибками.

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

Теперь b1 и b2 — совершенно разные объекты.

Используя deepcopy , можно переписать grow_rect так, чтобы вместо изменения существующего объекта Rectangle , он создавал новый объект Rectangle с таким же расположением левого верхнего угла, но с другими размерами:

12.11. Time¶

В качестве еще одного примера определенного пользователем типа, создадим класс Time (англ.: время) для хранения времени дня. Определение класса будет таким:

Теперь мы можем создать новый объект класса Time и установить значения атрибутов для часов, минут и секунд:

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

12.12. Снова чистые функции¶

Вот черновая версия функции add_time :

Функция создает новый объект Time , инициализирует его атрибуты, и возвращает ссылку на него. Это — чистая функция, поскольку она не изменяет ни один из переданных ей объектов и не имеет побочных эффектов, вроде вывода значения на печать или получения ввода от пользователя.

Вот пример использования этой функции. Мы создадим два объекта Time : current_time , содержащий текущее время, и bread_time , содержащий количество времени, необходимое хлебопечке для приготовления хлеба. Затем воспользуемся функцией add_time чтобы узнать, во сколько хлеб будет готов.

Определим функцию print_time для вывода объекта Time , воспользовавшись оператором форматирования сток:

Теперь выведем полученный нами результат:

Программа выводит 12:49:30 , и это правильный результат. Однако, в некоторых случаях результат работы функции add_time будет неверным. Можете сами привести пример такого случая?

Проблема с функцией add_time в том, что функция не учитывает случаи, когда сумма секунд или минут превышает 60. Когда это случается, необходимо выполнить перенос из переполнившегося разряда в разряд минут или часов.

Вот вторая, улучшенная, версия нашей функции:

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

12.13. Снова модифицирующие функции¶

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

Функцию increment , добавляющую указанное число секунд к объекту Time , наиболее естественно написать как модифицирующую. Вот ее черновая версия:

Первая строка выполняет основную операцию. Остальной код обрабатывает специальные случаи, которые мы обсудили выше.

Корректна ли эта функция? Что случится, если количество секунд, переданное функции, намного больше, чем 60? В этом случае недостаточно одного переноса 1 в разряд минут; мы должны выполнять переносы до тех пор, пока значение seconds продолжает быть меньше 60. Одно из возможных решений — заменить предложение if предложением while :

Теперь функция работает правильно, но это не самое эффективное решение.

12.14. Прототипирование и разработка дизайна программы¶

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

Хотя этот подход в целом эффективен, но, если пренебречь тщательным обдумыванием решаемой задачи, он может привести к излишне усложненному и ненадежному коду. Усложненному – поскольку придется иметь дело со многими специальными случаями. И ненадежному — поскольку нельзя утверждать, что все такие случаи учтены и все ошибки найдены.

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

В данном случае, анализ подскажет нам, что объект Time , представляющий количество времени, есть не что иное, как трехразрядное число с основанием 60! Действительно, секунды — это младший разряд единиц, минуты — разряд “шестидесяток”, а часы представлены самым старшим разрядом. “Единица” старшего разряда соответствует 3600 секундам.

Когда мы писали функции add_time и increment , мы на самом деле выполняли сложение в системе счисления с основанием 60, вот почему нам пришлось делать переносы из одного разряда в другой.

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

Все, что нам нужно теперь, — это способ преобразовать целое число обратно в Time :

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

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

12.15. Когда сложнее значит проще¶

Преобразование представления чисел из системы счисления с одним основанием в другую, и затем обратно, на первый взгляд кажется сложнее, чем прямое манипулирование привычными нам тремя компонентами времени: часами, минутами и секундами. А раз так, то не лучше ли полагаться на привычку, когда имеем дело со временем?

Но если мы нашли решение, основанное на представлении количества времени числом с основанием 60, и написали функции преобразования ( convert_to_seconds и make_time ), мы получаем более короткую и более надежную программу, которую легче читать и отлаживать.

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

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

9. Classes¶

Classes provide a means of bundling data and functionality together. Creating a new class creates a new type of object, allowing new instances of that type to be made. Each class instance can have attributes attached to it for maintaining its state. Class instances can also have methods (defined by its class) for modifying its state.

Compared with other programming languages, Python’s class mechanism adds classes with a minimum of new syntax and semantics. It is a mixture of the class mechanisms found in C++ and Modula-3. Python classes provide all the standard features of Object Oriented Programming: the class inheritance mechanism allows multiple base classes, a derived class can override any methods of its base class or classes, and a method can call the method of a base class with the same name. Objects can contain arbitrary amounts and kinds of data. As is true for modules, classes partake of the dynamic nature of Python: they are created at runtime, and can be modified further after creation.

In C++ terminology, normally class members (including the data members) are public (except see below Private Variables ), and all member functions are virtual. As in Modula-3, there are no shorthands for referencing the object’s members from its methods: the method function is declared with an explicit first argument representing the object, which is provided implicitly by the call. As in Smalltalk, classes themselves are objects. This provides semantics for importing and renaming. Unlike C++ and Modula-3, built-in types can be used as base classes for extension by the user. Also, like in C++, most built-in operators with special syntax (arithmetic operators, subscripting etc.) can be redefined for class instances.

(Lacking universally accepted terminology to talk about classes, I will make occasional use of Smalltalk and C++ terms. I would use Modula-3 terms, since its object-oriented semantics are closer to those of Python than C++, but I expect that few readers have heard of it.)

9.1. A Word About Names and Objects¶

Objects have individuality, and multiple names (in multiple scopes) can be bound to the same object. This is known as aliasing in other languages. This is usually not appreciated on a first glance at Python, and can be safely ignored when dealing with immutable basic types (numbers, strings, tuples). However, aliasing has a possibly surprising effect on the semantics of Python code involving mutable objects such as lists, dictionaries, and most other types. This is usually used to the benefit of the program, since aliases behave like pointers in some respects. For example, passing an object is cheap since only a pointer is passed by the implementation; and if a function modifies an object passed as an argument, the caller will see the change — this eliminates the need for two different argument passing mechanisms as in Pascal.

9.2. Python Scopes and Namespaces¶

Before introducing classes, I first have to tell you something about Python’s scope rules. Class definitions play some neat tricks with namespaces, and you need to know how scopes and namespaces work to fully understand what’s going on. Incidentally, knowledge about this subject is useful for any advanced Python programmer.

Let’s begin with some definitions.

A namespace is a mapping from names to objects. Most namespaces are currently implemented as Python dictionaries, but that’s normally not noticeable in any way (except for performance), and it may change in the future. Examples of namespaces are: the set of built-in names (containing functions such as abs() , and built-in exception names); the global names in a module; and the local names in a function invocation. In a sense the set of attributes of an object also form a namespace. The important thing to know about namespaces is that there is absolutely no relation between names in different namespaces; for instance, two different modules may both define a function maximize without confusion — users of the modules must prefix it with the module name.

By the way, I use the word attribute for any name following a dot — for example, in the expression z.real , real is an attribute of the object z . Strictly speaking, references to names in modules are attribute references: in the expression modname.funcname , modname is a module object and funcname is an attribute of it. In this case there happens to be a straightforward mapping between the module’s attributes and the global names defined in the module: they share the same namespace! 1

Attributes may be read-only or writable. In the latter case, assignment to attributes is possible. Module attributes are writable: you can write modname.the_answer = 42 . Writable attributes may also be deleted with the del statement. For example, del modname.the_answer will remove the attribute the_answer from the object named by modname .

Namespaces are created at different moments and have different lifetimes. The namespace containing the built-in names is created when the Python interpreter starts up, and is never deleted. The global namespace for a module is created when the module definition is read in; normally, module namespaces also last until the interpreter quits. The statements executed by the top-level invocation of the interpreter, either read from a script file or interactively, are considered part of a module called __main__ , so they have their own global namespace. (The built-in names actually also live in a module; this is called builtins .)

The local namespace for a function is created when the function is called, and deleted when the function returns or raises an exception that is not handled within the function. (Actually, forgetting would be a better way to describe what actually happens.) Of course, recursive invocations each have their own local namespace.

A scope is a textual region of a Python program where a namespace is directly accessible. “Directly accessible” here means that an unqualified reference to a name attempts to find the name in the namespace.

Although scopes are determined statically, they are used dynamically. At any time during execution, there are 3 or 4 nested scopes whose namespaces are directly accessible:

the innermost scope, which is searched first, contains the local names

the scopes of any enclosing functions, which are searched starting with the nearest enclosing scope, contain non-local, but also non-global names

the next-to-last scope contains the current module’s global names

the outermost scope (searched last) is the namespace containing built-in names

If a name is declared global, then all references and assignments go directly to the next-to-last scope containing the module’s global names. To rebind variables found outside of the innermost scope, the nonlocal statement can be used; if not declared nonlocal, those variables are read-only (an attempt to write to such a variable will simply create a new local variable in the innermost scope, leaving the identically named outer variable unchanged).

Usually, the local scope references the local names of the (textually) current function. Outside functions, the local scope references the same namespace as the global scope: the module’s namespace. Class definitions place yet another namespace in the local scope.

It is important to realize that scopes are determined textually: the global scope of a function defined in a module is that module’s namespace, no matter from where or by what alias the function is called. On the other hand, the actual search for names is done dynamically, at run time — however, the language definition is evolving towards static name resolution, at “compile” time, so don’t rely on dynamic name resolution! (In fact, local variables are already determined statically.)

A special quirk of Python is that – if no global or nonlocal statement is in effect – assignments to names always go into the innermost scope. Assignments do not copy data — they just bind names to objects. The same is true for deletions: the statement del x removes the binding of x from the namespace referenced by the local scope. In fact, all operations that introduce new names use the local scope: in particular, import statements and function definitions bind the module or function name in the local scope.

The global statement can be used to indicate that particular variables live in the global scope and should be rebound there; the nonlocal statement indicates that particular variables live in an enclosing scope and should be rebound there.

9.2.1. Scopes and Namespaces Example¶

This is an example demonstrating how to reference the different scopes and namespaces, and how global and nonlocal affect variable binding:

The output of the example code is:

Note how the local assignment (which is default) didn’t change scope_test‘s binding of spam. The nonlocal assignment changed scope_test‘s binding of spam, and the global assignment changed the module-level binding.

You can also see that there was no previous binding for spam before the global assignment.

9.3. A First Look at Classes¶

Classes introduce a little bit of new syntax, three new object types, and some new semantics.

9.3.1. Class Definition Syntax¶

The simplest form of class definition looks like this:

Class definitions, like function definitions ( def statements) must be executed before they have any effect. (You could conceivably place a class definition in a branch of an if statement, or inside a function.)

In practice, the statements inside a class definition will usually be function definitions, but other statements are allowed, and sometimes useful — we’ll come back to this later. The function definitions inside a class normally have a peculiar form of argument list, dictated by the calling conventions for methods — again, this is explained later.

When a class definition is entered, a new namespace is created, and used as the local scope — thus, all assignments to local variables go into this new namespace. In particular, function definitions bind the name of the new function here.

When a class definition is left normally (via the end), a class object is created. This is basically a wrapper around the contents of the namespace created by the class definition; we’ll learn more about class objects in the next section. The original local scope (the one in effect just before the class definition was entered) is reinstated, and the class object is bound here to the class name given in the class definition header ( ClassName in the example).

9.3.2. Class Objects¶

Class objects support two kinds of operations: attribute references and instantiation.

Attribute references use the standard syntax used for all attribute references in Python: obj.name . Valid attribute names are all the names that were in the class’s namespace when the class object was created. So, if the class definition looked like this:

then MyClass.i and MyClass.f are valid attribute references, returning an integer and a function object, respectively. Class attributes can also be assigned to, so you can change the value of MyClass.i by assignment. __doc__ is also a valid attribute, returning the docstring belonging to the class: "A simple example class" .

Class instantiation uses function notation. Just pretend that the class object is a parameterless function that returns a new instance of the class. For example (assuming the above class):

creates a new instance of the class and assigns this object to the local variable x .

The instantiation operation (“calling” a class object) creates an empty object. Many classes like to create objects with instances customized to a specific initial state. Therefore a class may define a special method named __init__() , like this:

When a class defines an __init__() method, class instantiation automatically invokes __init__() for the newly created class instance. So in this example, a new, initialized instance can be obtained by:

Of course, the __init__() method may have arguments for greater flexibility. In that case, arguments given to the class instantiation operator are passed on to __init__() . For example,

9.3.3. Instance Objects¶

Now what can we do with instance objects? The only operations understood by instance objects are attribute references. There are two kinds of valid attribute names: data attributes and methods.

data attributes correspond to “instance variables” in Smalltalk, and to “data members” in C++. Data attributes need not be declared; like local variables, they spring into existence when they are first assigned to. For example, if x is the instance of MyClass created above, the following piece of code will print the value 16 , without leaving a trace:

The other kind of instance attribute reference is a method. A method is a function that “belongs to” an object. (In Python, the term method is not unique to class instances: other object types can have methods as well. For example, list objects have methods called append, insert, remove, sort, and so on. However, in the following discussion, we’ll use the term method exclusively to mean methods of class instance objects, unless explicitly stated otherwise.)

Valid method names of an instance object depend on its class. By definition, all attributes of a class that are function objects define corresponding methods of its instances. So in our example, x.f is a valid method reference, since MyClass.f is a function, but x.i is not, since MyClass.i is not. But x.f is not the same thing as MyClass.f — it is a method object, not a function object.

9.3.4. Method Objects¶

Usually, a method is called right after it is bound:

In the MyClass example, this will return the string ‘hello world’ . However, it is not necessary to call a method right away: x.f is a method object, and can be stored away and called at a later time. For example:

will continue to print hello world until the end of time.

What exactly happens when a method is called? You may have noticed that x.f() was called without an argument above, even though the function definition for f() specified an argument. What happened to the argument? Surely Python raises an exception when a function that requires an argument is called without any — even if the argument isn’t actually used…

Actually, you may have guessed the answer: the special thing about methods is that the instance object is passed as the first argument of the function. In our example, the call x.f() is exactly equivalent to MyClass.f(x) . In general, calling a method with a list of n arguments is equivalent to calling the corresponding function with an argument list that is created by inserting the method’s instance object before the first argument.

If you still don’t understand how methods work, a look at the implementation can perhaps clarify matters. When a non-data attribute of an instance is referenced, the instance’s class is searched. If the name denotes a valid class attribute that is a function object, a method object is created by packing (pointers to) the instance object and the function object just found together in an abstract object: this is the method object. When the method object is called with an argument list, a new argument list is constructed from the instance object and the argument list, and the function object is called with this new argument list.

9.3.5. Class and Instance Variables¶

Generally speaking, instance variables are for data unique to each instance and class variables are for attributes and methods shared by all instances of the class:

As discussed in A Word About Names and Objects , shared data can have possibly surprising effects with involving mutable objects such as lists and dictionaries. For example, the tricks list in the following code should not be used as a class variable because just a single list would be shared by all Dog instances:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *