Терминология в Классах Python
Я запутался в формулировках в классах Python, может у кого то есть картинка объясняющая разницу?
В классах в Python есть:
Переменные — которые называются атрибутами или же свойствами класса. Функции — которые называются методами. Экземпляры (они же обьекты) — это что? Тоже переменные? Поля класса — это свойство, или же просто атрибут. Поле класса так же является переменной? Члены класса — атрибут. Тоже переменная?
Мешанина этих слов создает кашу в голове и непонимание. Помогите разобраться.
Картинки, к сожалению, нет. К тому же, сдаётся мне, что, прочитав раздел ООП в какой-нибудь книжке по Python, Вы с этим разберётесь, но всё же оставлю здесь своё субъективное понимание представленных терминов.
- Начать лучше, думаю, с объектов (экземпляров). Поясню на примере. Класс – это тип. Пусть у нас есть класс "Компьютер". Тогда объектами этого класса будут "мой компьютер", "Ваш компьютер", "Вон тот комп, что мне понравился в магазине". То есть, некоторые конкретные представители данного класса. Или же есть класс "Человек". Объектами будут Вася, Петя, Коля и т.д. Если перейти к Python:
- Атрибуты, свойства, поля класса – это плюс/минус одно и то же (Возможно кто-то пояснит тонкие различия, но, это всё-таки моё видение, а я вижу их как одно и то же, по крайней мере если брать среднее для всех языков. В некоторых Свойство – это нечто отдельное). Это переменная, существующая внутри объекта этого класса и содержащая в себе значение, отражающее некоторое свойство этого объекта. Если вернуться к нашему примеру, атрибуты класса "Человек" это, например, возраст, рост, пол, фамилия и т.д. Если к коду:
(в Python, в отличие от C++, например, атрибуты обычно не объявляются заранее (хотя я не эксперт в Python, могу ошибаться), а просто вводятся как переменные с self. в конструкторе или другом методе).
Заметки об объектной системе языка 1¶
Несколько заметок об объектной системе python’a. Рассчитаны на тех, кто уже умеет программировать на python. Речь идет только о новых классах (new-style classes) в python 2.3 и выше. В этой статье рассказывается, что такое объекты и как происходит поиск атрибутов.
Объекты¶
Все данные в питоне — это объекты. Каждый объект имеет 2 специальных атрибута __class__ и __dict__. __class__ — определяет класс или тип, экзмепляром которого является объект. Тип (или класс объекта) определяет его поведение; он есть у всех объектов, в том числе и встроенных. Тип и класс — это разные названия одного и того же. x.__class__ <==> type(x). __dict__ словарь, дающий доступ к внутреннему пространству имен, он есть почти у всех объектов, у многих встроенных типов его нет. Примеры.
У a тоже есть __dict__ и __class__:
Класс и тип — это одно и то же.
a.__dict__ — это словарь, в котором находятся внутренние (или специфичные для объекта) атрибуты, в данном случае ‘name’. А в a.__class__ класс (тип).
И, например, в методах класса присваивание self.foo = bar практически идентично self.__dict__[‘foo’] = bar или сводится к аналогичному вызову.
В __dict__ объекта нет методов класса, дескрипторов, классовых переменных, свойств, статических методов класса, все они определяются динамически с помощью класса из __class__ атрибута, и являются специфичными именно для класса (типа) объекта, а не для самого объекта.
Пример. Переопределим класс объекта a:
Смотрим, что поменялось.
Значение a.name осталось прежним, т.е. __init__ не вызывался при смене класса.
Работа с атрибутам объекта: установка, удаление и поиск, равносильна вызову встроенных функций settattr, delattr, getattr:
При этом стоит стоит понимать, что setattr и delattr влияют и изменяют только сам объект (точнее a.__dict__), и не изменяют класс объекта.
qux — является классовой переменной, т.е. она «принадлежит» классу B, а не объекту a:
Если мы попытаемся удалить этот атрибут, то получим ошибку, т.к. delattr будет пытаться удалить атрибут из a.__dict__
Далее, если мы попытаемся изменить (установить) атрибут, setattr поместит его в __dict__, специфичный для данного, конкретного объекта.
Ну и раз есть ‘qux’ в __dict__ объекта, его можно удалить с помощью delattr:
После удаления, a.qux будет возвращать значение классовой переменной:
Итак: класс для объекта — это значение специального атрибута __class__ и его можно менять. (Хотя в официальной документации говорится, что никаких гарантий нет, но на самом деле можно) почти каждый объект имеет свое пространство имен (атрибутов), доступ (не всегда полный), к которому осуществляется с помощью специального атрибута __dict__ класс фактичеки влияет только на поиск атрибутов, которых нет в __dict__, как-то: методы класса, дескрипторы, магические методы, классовые переменные и прочее.
Объекты и классы¶
Классы — это объекты, и у них тоже есть специальные атрибуты __class__ и __dict__.
У класса тип type.
Правда __dict__ у классов не совсем словарь
Но __dict__ ответственен за доступ к внутреннему пространству имен, в котором хранятся методы, дескрипторы, переменные, свойства и прочее:
В классах помимо __class__ и __dict__, имеется еще несколько специальных атрибутов: __bases__ — список прямых родителей, __name__ — имя класса. [1]
Классы можно считать эдакими расширениями обычных объектов, которые реализуют интерфейс типа. Множество всех классов (или типов) принадлежат множеству всех объектов, а точнее является его подмножеством. Иначе говоря, любой класс является объектом, но не всякий объект является классом. Договоримся называть обычными объектами(regular objects) те объекты, которые классами не являются.
Небольшая демонстрация, которая станет лучше понятна чуть позже. Класс является объектом. >>> class A(object): . pass .
Число — это тоже объект.
Класс — это класс (т.е. тип).
А вот число классом (типом) не является. (Что такое type будет пояснено позже)
Ну и a — тоже обычный объект.
И у A всего один прямой родительский класс — object.
Часть специальных параметров можно даже менять:
С помощью getattr получаем доступ к атрибутам класса:
Поиск атрибутов в обычном объекте¶
В первом приближении алгоритм поиска выглядит так: сначала ищется в __dict__ объекта, потом идет поиск по __dict__ словарям класса объекта (который определяется с помощью __class__) и __dict__ его базовых классов в рекурсивном порядке.
Т.к. в обычных объектах a и b нет в __dict__ атрибута ‘qux’, то поиск продолжается во внутреннем словаре __dict__ их типа (класса), а потом по __dict__ словарям родителей в определенном порядке:
Меняем атрибут qux у класса A. И соответственно должны поменяться значения, которые возвращают экземпляры класса A — a и b:
Точно так же в рантайме к классу можно добавить метод:
И доступ к нему появится у экземпляров:
Точно так же как и с любыми другими объектами, можно удалить атрибут класса, например, классовую переменную qux:
Она удалиться из __dict__
И доступ у экземляров пропадет.
У классов почти такой же поиск атрибутов, как и у обычных объектов, но есть отличия: поиск начинается с собственного __dict__ словаря, а потом идет поиск по __dict__ словарям суперклассов (которые хранятся в __bases__) по опредленному алгоритму, а затем по классу в __class__ и его суперклассах. (Подробнее об этом позже).
Cсылки¶
- Unifying types and classes in Python — главный документ, объясняющий что, как и зачем в новых классах. http://www.python.org/download/releases/2.2.3/descrintro/#references
- Making Types Look More Like Classes — PEP 252, описывающий отличие старых классов от новых. http://www.python.org/dev/peps/pep-0252/
- Built-in functions — детальное описание работы всех встроенных функций. http://docs.python.org/library/functions.html
- Data model — детальное описание модели данных python’а. http://docs.python.org/reference/datamodel.html
- Python types and objects — объяснение объектной модели python на простых примерах с картинками. http://www.cafepy.com/article/python_types_and_objects/python_types_and_objects.html
Примечания¶
[1] О __module__ и __doc__ для простоты изложения пока забудем. Полный список атрибутов класса можно посмотреть в документации http://docs.python.org/reference/datamodel.html#the-standard-type-hierarchy
Заметки об объектной системе языка 2¶
Вторая часть заметок об объектной системе python’a (первая часть тут). В этой статье рассказывается, что такое классы, метаклассы, type, object и как происходит поиск атрибутов в классе.
Классы¶
Классы (типы) — это объектные фабрики. Их главная задача — создавать объекты, обладающие определенным поведением.
Классы определяют поведение объектов с помощью своих атрибутов (которые хранятся в __dict__ класса): методов, свойств, классовых переменные, дескрипторов, а также с помощью атрибутов, унаследованных от родительских классов.
Инстанцирование обычного объекта происходит в 2 этапа: сначала его создание, потом инициализация. Соответственно, сначала запускается метод класса __new__, который возвращает объект данного класса, потом выполняется метод класса __init__, который инициализирует уже созданный объект.
def __new__(cls, . ) — статический метод (но его можно таковым не объявлять), который создает объект класса cls.
def __init__(self, . ) — метод класса, который инициализирует созданный объект.
Например, объявим класс:
Для класса A не определены ни __new__, ни __init__. В соответствии с алгоритмом поиска атрибутов для класса (типа), который не стоит путать с алгоритмом поиска атрибутов для обычных объектов, когда класс не найдет их в своем__dict__, он будет искать эти методы в __dict__ своих базовых (родительских) классах.
Класс А имеет в качестве родителя встроенный класс object. Таким образом он будет их искать в object.__dict__
Раз есть такие методы, значит, получается, что a = A() аналогичен последовательности вызовов:
a = object.__new__(A) object.__init__(a)
В общем виде, используя super, который как раз и реализует алгоритм поиска атрибутов по родительским классам [1]:
a = super(A, A).__new__(A) super(A, A).__init__(a)
Singleton v.1¶
Понимая, как происходит создание объекта, можно написать реализацию паттерна одиночка.
Мы должны гарантировать, что у класса есть только один экземпляр. Т.е. при вызове конструктора класса, всегда возвращаем один и тот же экземпляр класса.
А это значит, что при вызов метода __new__ должен возвращать каждый раз один и тот же объект. Хранить сам объект можно, например, в классовой переменной instance.
В результате получаем:
Классы и метаклассы¶
Для класса (типа), так же как и для обычного объекта, существует класс (тип), который создает классы и определяет поведение класса. Этот класс называется метаклассом.
Создание класса, как и обычного объекта происходит с помощью вызова конструктора, но т.к. в классе есть несколько дополнительных специальных атрибутов, которые должны быть инициализированы, в конструктор передаются и соответствующие обязательные параметры.
XClass = XMetaClass(name, bases, attrs)
Тогда, сразу после создания XClass.__name__ равно name, XClass.__bases__ равен bases, XClass.__dict__ равен attrs, а XClass.__class__ равен XMetaClass
По умолчанию для всех определяемых классов метаклассом является type.
Эквивалентно, по аналогии с обычными объектами:
При определении класса, можно задать свой метакласс с помощью классовой переменной __metaclass__:
Что равносильно: A = Meta(‘A’, (object,), <>)
О type и object¶
Прежде всего type и object — это объекты. И, как у всех порядочных объектов, у них есть специальные атрибуты __class__ и __dict__:
Более того object, и type — это объекты типа (классы), и у них тоже есть специальные атрибуты __name__, __bases___:
Экземпляры типа или класса object — это объекты (любые). Т.е. любой объект — экземпляр класса object:
Даже функция является объектом: >>> def bar(): . pass . >>> isinstance(bar, object) True
Кроме того, класс object сам является своим экземпляром:
type тоже является его экземпляром:
Инстанцирование — object() возвращает самый простой и общий объект:
У которого даже __dict__ нет, есть только __class__.
Экземпляры класса или типа type — это только другие классы или другие типы:
Число — это не класс
Встроенная функция setattr тоже не класс.
Класс — это класс.
Тип строки — это класс.
Т.к. object и type — тоже классы, то они являются экземплярами класса type:
Т.к. множество классов (типов) являются подмножеством множества объектов, то логично предположить, что type является подклассом object, т.е.
type — это просто класс, экземплярами которого являются другие классы. (т.е. метакласс). А сами классы можно считать расширением простых, обычных объектов.
Таким образом, когда мы наследуем класс от object, этот класс автоматически наследует поведение класса object, т.е. при инстанцировании он будет возвращать обычный объект. А когда мы наследуем от класса type, мы также автоматически наследуем поведение класса type, т.е. при инстацированни будет создаваться класс. А класс, который создает класс, называется метаклассом.
Значит, чтобы определить просто класс, нужно наследовать его от object, чтобы определить метакласс — наследуем его от type.
И еще: не нужно путать type(a) и type(name, bases, attrs). type(a) — вызов с одним аргументом, возвращает тип объекта, a type(name, bases, attrs) — вызов с тремя аргументами — это вызов конструктора класса.
О поиске атрибутов в классе
Как уже было отмечено, алгоритм поиска атрибутов в обычном объекте, но есть некоторые тонкости, т.к. у типов (классов) есть __bases__ — родительские классы (типы).
Если атрибут есть в __dict__ возвращается он, затем идет поиск по базовым классам из __bases__, а потом идет обращение к __dict__ __class__’а (т.е. фактически метакласса) и его (метакласса) родительских классов (метаклассов).
Все что определяется в метаклассе доступно для класса, но не доступно для экзмепляров класса — обычных объектов, т.к. поиск атрибутов в обычном объекте ведется только по __dict__ словарям класса.
# Создание классов и объектов
В языке программирования Python классы создаются с помощью команды class, за которой следует произвольное имя класса, после которого ставится двоеточие, далее с новой строки и с отступом реализуется тело класса:
Если класс является дочерним, то родительские классы перечисляются в круглых скобках после имени класса.
Объект создается путем вызова класса по его имени. При этом после имени класса обязательно ставятся скобки:
То есть класс вызывается подобно функции. Однако при этом происходит не выполнение его тела, а создается объект. Поскольку в программном коде важно не потерять ссылку на только что созданный объект, то обычно его связывают с переменной. Поэтому создание объекта чаще всего выглядит так:
В последствии к объекту обращаются через связанную с ним переменную.
Пример «пустого» класса и двух созданных на его основе объектов:
Оператор pass не делает ничего. Он может использоваться когда синтаксически требуется присутствие оператора, но от программы не требуется действий.
# Класс как модуль
В языке программирования Python класс можно представить подобным модулю. Также как в модуле в нем могут быть свои переменные со значениями и функции. Также как в модуле у класса есть собственное пространство имен, доступ к которым возможен через имя класса:
Однако в случае классов используется немного иная терминология. Пусть имена, определенные в классе, называются атрибутами этого класса. В примере имена n и adder – это атрибуты класса B. Атрибуты-переменные часто называют полями или свойствами. Свойством является n. Атрибуты-функции называются методами. Методом в классе B является adder. Количество свойств и методов в классе может быть любым.
# Класс как создатель объектов
Приведенный выше класс позволяет создавать объекты, но мы не можем применить к объекту класса метод adder():
Вывод в консоль:
В сообщении об ошибке говорится, что adder() принимает только один аргумент, а было передано два. Откуда взялся второй аргумент, и кто он такой, если в скобках было указано только одно число 100?
На самом деле классы – это далеко не модули. Они идут дальше модулей и обладают своими особенностями. Класс создает объекты, которые в определенном смысле являются его наследниками. Это значит, что если у объекта нет собственного поля n, то интерпретатор ищет его уровнем выше, то есть в классе. Таким образом, если мы присваиваем объекту поле с таким же именем как в классе, то оно перекрывает, т. е. переопределяет, поле класса:
Здесь l.n и B.n – это разные переменные. Первая находится в пространстве имен объекта l. Вторая – в пространстве класса B. Если бы мы не добавили поле n к объекту l, то интерпретатор бы поднялся выше по дереву наследования и пришел бы в класс, где бы и нашел это поле.
Что касается методов, то они также наследуются объектами класса. В данном случае у объекта l нет своего собственного метода adder, значит, он ищется в классе B. Однако от класса B может быть порождено множество объектов. Методы же чаще всего предназначаются для обработки объектов. Таким образом, когда вызывается метод, в него надо передать конкретный объект, который он будет обрабатывать.
Понятно, что передаваемый экземпляр, это объект, к которому применяется метод. Выражение l.adder(100) выполняется интерпретатором следующим образом:
- Ищу атрибут adder() у объекта l. Не нахожу.
- Тогда иду искать в класс B, так как он создал объект l.
- Здесь нахожу искомый метод. Передаю ему объект, к которому этот метод надо применить, и аргумент, указанный в скобках.
Другими словами, выражение l.adder(100) преобразуется в выражение B.adder(l, 100).
Таким образом, интерпретатор попытался передать в метод adder() класса B два параметра – объект l и число 100. Но мы запрограммировали метод adder() так, что он принимает только один параметр. В Python, да и многих других языках, определения методов не предполагают принятие объекта как само собой подразумеваемое. Принимаемый объект надо указывать явно.
По соглашению в Python для ссылки на объект используется имя self. Вот так должен выглядеть метод adder(), если мы планируем вызывать его через объекты:
Переменная self связывается с объектом, к которому был применен данный метод, и через эту переменную мы получаем доступ к атрибутам объекта. Когда этот же метод применяется к другому объекту, то self свяжется уже с этим другим объектом, и через эту переменную будут извлекаться только его свойства.
Протестируем обновленный метод:
Здесь от класса B создаются два объекта – l и m. Для объекта l заводится собственное поле n. Объект m, за неимением собственного, наследует n от класса B. Можно в этом убедиться, проверив соответствие:
В методе adder() выражение self.n – это обращение к свойству n, переданного объекта, и не важно, на каком уровне наследования оно будет найдено. Операции is или is not проверяют, не являются ли два объекта на самом деле одним и тем же.
Если метод не принимает объект, к которому применяется, в качестве первого параметра, то такие методы в других языках программирования называются статическими. Они имеют особый синтаксис и могут вызываться как через класс, так и через объект этого класса. В Python все немного по-другому. Для имитации статических методов используется специальный декоратор, после чего метод можно вызывать не только через класс, но и через объект, не передавая сам объект.
# Изменение полей объекта
В Python объекту можно не только переопределять поля и методы, унаследованные от класса, также можно добавить новые, которых нет в классе:
В консоль выведет ошибку: «объект В не имеет атрибута „test“»:
Однако в программировании так делать не принято, потому что тогда объекты одного класса будут отличаться между собой по набору атрибутов. Это затруднит автоматизацию их обработки, внесет в программу хаос.
Поэтому принято присваивать полям, а также получать их значения, путем вызова методов:
Подобные методы в простонародье называют сеттерами геттерами (get – получить) и (set – установить).
# Упражнения
Программу в которой класс «Животное» с полями тип и кличка:
Создайте ещё 3 экземпляра данного класса присвоив им тип и кличку. Выведите значения полей созданного объекта.
Добавим в класс «Животное» поля «Очки Жизней» и методы: «кушать», который увеличивает количество очков жизней на 10; «жизнь», который уменьшает количество очков жизней на переданное методу значение. Добавит метод возвращающий текущее значение жизней или над «хочу есть», если значение жизней меньше или равно 0:
Создайте еще пару объектов это класса и попробуйте применить к ним написанные методы и понять принцип работы кода.
Напишите программу в которой есть класс автомобиль с полями например: марка, модель, цвет. И создайте 3 экземпляра класса. И выведите значения полей в консоль.
Добавьте к классу автомобиль поле «топливо» и методы «заправить» и «ехать», которые будут принимать параметры пополнять или уменьшать объем топлива машины. Метод ехать должен проверять наличие топлива и если его осталось меньше нуля, то выводиться: «бензин кончился».
Напишите программу по следующему описанию. Есть класс «Воин». От него создаются два экземпляра-юнита. Каждому устанавливается здоровье в 100 очков. В случайном порядке они бьют друг друга. Тот, кто бьет, здоровья не теряет. У того, кого бьют, оно уменьшается на 20 очков от одного удара. После каждого удара надо выводить сообщение, какой юнит атаковал, и сколько у противника осталось здоровья. Как только у кого-то заканчивается ресурс здоровья, программа завершается сообщением о том, кто одержал победу.
Объектно-ориентированное Программирование в Python
Объектно-ориентированное программирование (ООП) — это парадигма программирования, где различные компоненты компьютерной программы моделируются на основе реальных объектов. Объект — это что-либо, у чего есть какие-либо характеристики и то, что может выполнить какую-либо функцию.
Содержание
Представьте сценарий, где вам нужно разработать болид Формулы-1 используя подход объектно-ориентированного программирования. Первое, что вам нужно сделать — это определить реальные объекты в настоящей гонке Формула-1. Какие аспекты в Формуле-1 обладают определенными характеристиками и могут выполнять ту или иную функцию?
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Telegram Чат & Канал
Вступите в наш дружный чат по Python и начните общение с единомышленниками! Станьте частью большого сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
Один из очевидных ответов на этот вопрос — гоночный болид. Условный болид может обладать такими характеристиками как:
- мощность двигателя;
- марка;
- модель;
- производитель, и т. д.
Соответственно, болид можно запустить, остановить, ускорить, и так далее. Гонщик может быть еще одним объектом в Формуле-1. Гонщик имеет национальность, возраст, пол, и так далее, кроме этого, он обладает таким функционалом, как управление болидом, рулевое управление, переключение передач.
Как и в этом примере, в объектно-ориентированном программировании мы создадим объекты, которые будут соответствовать реальным аспектам.
Стоит обратить внимание на то, что объектно-ориентированное программирование — не зависящая от языка программирования концепция. Это общая концепция программирования и большинство современных языков, такие как Java, C#, C++ и Python поддерживают объектно-ориентированное программирование.
В этой статье мы разберем подробную инструкцию объектно-ориентированного программирования в Python, но перед этим, рассмотрим некоторые преимущества и недостатки объектно-ориентированного программирования.
Преимущества и недостатки ООП Python
Рассмотрим несколько основных преимуществ объектно-ориентированного программирования:
- Объектно-ориентированное программирование подразумевает повторное использование. Компьютерная программа написанная в форме объектов и классов может быть использована снова в других проектах без повторения кода;
- Использование модулярного подхода в объектно-ориентированном программировании позволяет получить читаемый и гибкий код;
- В объектно-ориентированном программировании каждый класс имеет определенную задачу. Если ошибка возникнет в одной части кода, вы можете исправить ее локально, без необходимости вмешиваться в другие части кода;
- Инкапсуляция данных (которую мы рассмотрим дальше в статье) вносит дополнительный уровень безопасности в разрабатываемую программу с использованием объектно-ориентированного подхода;
Хотя объектно-ориентированное программирование обладает рядом преимуществ, оно также содержит определенные недостатки, некоторые из них находятся в списке ниже:
- Для создания объектов необходимо иметь подробное представление о разрабатываемом программном обеспечении;
- Не каждый аспект программного обеспечения является лучшим решением для реализации в качестве объекта. Для новичков может быть тяжело прочертить линию в золотой середине;
- С тем, как вы вносите все новые и новые классы в код, размер и сложность программы растет в геометрической прогрессии;
В следующем разделе мы рассмотрим ряд самых важных концепций объектно-ориентированного программирования.
Как и следует из названия, объектно-ориентированное программирование — это речь об объектах. Однако, перед тем как создать объект, нам нужно определить его класс.
Класс
Класс в объектно-ориентированном программировании выступает в роли чертежа для объекта. Класс можно рассматривать как карту дома. Вы можете понять, как выглядит дом, просто взглянув на его карту.
Cам по себе класс не представляет ничего. К примеру, нельзя сказать что карта является домом, она только объясняет как настоящий дом должен выглядеть.
Отношение между классом и объектом можно представить более наглядно, взглянув на отношение между машиной и Audi. Да, Audi – это машина. Однако, нет такой вещи, как просто машина. Машина — это абстрактная концепция, которую также реализуют в Toyota, Honda, Ferrari, и других компаниях.
Ключевое слово class используется для создания класса в Python. Название класса следует за ключом class , за которым следует двоеточие. Тело класса начинается с новой строки, с отступом на одну вкладку влево.
Давайте рассмотрим, как мы можем создать самый простой класс в Python. Взглянем на следующий код: