Объектно-ориентированное программирование ⚡️ Python 3 с примерами кода
До сих пор наши программы состояли из функций, т. е. блоков выражений, которые манипулируют данными. Это называется процедурно-ориентированным стилем программирования. Существует и другой способ организации программ: объединять данные и функционал внутри некоего объекта. Это называется объектно-ориентированной парадигмой программирования. В большинстве случаев можно ограничиться процедурным программированием, а при написании большой программы или если решение конкретной задачи того требует, можно переходить к техникам объектно-ориентированного программирования.
Два основных аспекта объектно-ориентированного программирования — классы и
объекты. Класс создаёт новый тип, а объекты являются экземплярами класса. Аналогично, когда мы говорим о «переменных типа int
«,
это означает, что переменные, которые хранят целочисленные значения, являются
экземплярами (объектами) класса int
Замечание для программистов на статических языках
Обратите внимание, что даже целые числа рассматриваются как объекты (класса int
), в отличие от C++ и Java (до версии 1.5), где целые числа являются
примитивами. См. help(int)
для более детального описания этого класса.
Программисты на C# и Java 1.5 могут заметить сходство с концепцией упаковки и распаковки1.
Объекты могут хранить данные в обычных переменных, которые принадлежат объекту. Переменные, принадлежащие объекту или классу, называют полями. Объекты могут также обладать функционалом, т. е. иметь функции, принадлежащие классу. Такие функции принято называть методами класса. Эта терминология важна, так как она помогает нам отличать независимые функции и переменные от тех, что принадлежат классу или объекту. Всё вместе (поля и методы) принято называть атрибутами класса.
Поля бывают двух типов: они могут принадлежать каждому отдельному экземпляру объекта класса или всему классу. Они называются переменными экземпляра и переменными класса соответственно.
Класс создаётся ключевым словом class
. Поля и методы класса записываются в
блоке кода с отступом.
self
Методы класса имеют одно отличие от обычных функций: они должны иметь
дополнительно имя, добавляемое к началу списка параметров. Однако, при вызове
метода никакого значения этому параметру присваивать не нужно — его укажет
Python. Эта переменная указывает на сам объект экземпляра класса, и по
традиции она называется self
2.
Хотя этому параметру можно дать любое имя, настоятельно рекомендуется использовать только имя self
; использование любого другого имени не
приветствуется. Есть много достоинств использования стандартного имени:
во-первых, любой человек, просматривающий вашу программу, легко узнает его;
во-вторых, некоторые специализированные Интегрированные среды разработки (IDE)
изначально рассчитаны на использование
.
Замечание для программистов на C++, Java и C#
self
в Python эквивалентно указателю this
в C++ и ссылке this
в Java и C#.
Вы, должно быть, удивляетесь, как Python присваивает значение self
и почему
вам не нужно указывать это значение самостоятельно. Поясним это на примере.
Предположим, у нас есть класс с именем MyClass
и экземпляр этого класса с
именем myobject
. При вызове метода этого объекта, например,
«myobject.method(arg1, arg2)
«, Python автоматически превращает это в
«MyClass.method(myobject, arg1, arg2)
self
.Это также означает, что если какой-либо метод не принимает аргументов, у него
всё равно будет один аргумент — self
.
Классы
Простейший класс показан в следующем примере (сохраните как simplestclass.py
).
class Person: pass # Пустой блок p = Person() print(p)
Вывод:
$ python3 simplestclass. py <__main__.Person object at 0x019F85F0>
Как это работает:
Мы создаём новый класс при помощи оператора class
и имени класса. За
этим следует блок выражений, формирующих тело класса. В данном случае блок
у нас пуст, на что указывает оператор pass
.
Далее мы создаём объект-экземпляр класса, записывая имя класса со скобками.
(Мы узнаем больше о реализации в следующем разделе). Для
проверки мы выясняем тип переменной, просто выводя её на экран. Так мы
видим, что у нас есть экземпляр класса
в модуле __main__
.
Обратите внимание, что выводится также и адрес в памяти компьютера, где хранится ваш объект. На вашем компьютере адрес будет другим, так как Python хранит объекты там, где имеется свободное место.
Методы объектов
Итак, мы выяснили что классы/объекты могут иметь методы, представляющие собой
функции, за исключением дополнительной переменной self
. А теперь давайте
рассмотрим пример (сохраните как method. py
).
class Person: def sayHi(self): print('Привет! Как дела?') p = Person() p.sayHi() # Этот короткий пример можно также записать как Person().sayHi()
Вывод:
$ python3 method.py Привет! Как дела?
Как это работает:
Здесь мы видим self
в действии. Обратите внимание, что метод sayHi
не
принимает параметров, но тем не менее, имеет self
в определении функции.
Метод
__init__
Существует много методов, играющих специальную роль в классах Python. Сейчас мы
увидим значительность метода __init__
.
Метод __init__
запускается, как только объект класса реализуется. Этот
метод полезен для осуществления разного рода инициализации, необходимой для
данного объекта. Обратите внимание на двойные подчёркивания в начале и в конце
имени.
Пример: (сохраните как oop_init.py
)
class Person: def __init__(self, name): self. name = name def say_hi(self): print('Привет! Меня зовут', self.name) p = Person('Swaroop') p.say_hi() # Предыдущие 2 строки можно # Person('Swaroop').say_hi()
Вывод:
$ python oop_init.py Привет! Меня зовут Swaroop
Как это работает:
Здесь мы определяем метод __init__
так, чтобы он принимал параметр name
(наряду с обычным self
). Далее мы создаём новое поле с именем name
. Обратите внимание, что это две разные переменные, даже несмотря
на то, что они обе названы name
. Это не проблема, так как точка в
выражении self.name
обозначает, что существует нечто с именем «name»,
являющееся частью объекта «self», и другое name
— локальная переменная.
Поскольку мы в явном виде указываем, к которому имени мы обращаемся,
путаницы не возникнет.
Для создания нового экземпляра p
класса Person
мы указываем имя
класса, после которого — аргументы в скобках: p = Person('Swaroop')
.
Метод __init__
мы при этом не вызываем явным образом. В этом и
заключается специальная роль данного метода.
После этого мы получаем возможность использовать поле self.name
в наших
методах, что и продемонстрировано в методе say_hi
.
Переменные класса и объекта
Функциональную часть классов и объектов (т. е. методы) мы обсудили, теперь давайте ознакомимся с частью данных. Данные, т. е. поля, являются не чем иным, как обычными переменными, заключёнными в пространствах имён классов и объектов. Это означает, что их имена действительны только в контексте этих классов или объектов. Отсюда и название «пространство имён«.
Существует два типа полей: переменные класса и переменные объекта, которые различаются в зависимости от того, принадлежит ли переменная классу или объекту соответственно.
Переменные класса разделяемы — доступ к ним могут получать все экземпляры этого класса.
Переменные объекта принадлежат каждому отдельному экземпляру класса. В этом
случае у каждого объекта есть своя собственная копия поля, т.е. не разделяемая
и никоим образом не связанная с другими такими же полями в других экземплярах.
Это легко понять на примере (сохраните как objvar.py
):
class Robot: '''Представляет робота с именем.''' # Переменная класса, содержащая количество роботов population = 0 def __init__(self, name): '''Инициализация данных.''' self.name = name print('(Инициализация {0})'.format(self.name)) # При создании этой личности, робот добавляется # к переменной 'population' Robot.population += 1 def __del__(self): '''Я умираю.''' print('{0} уничтожается!'.format(self.name)) Robot. population -= 1 if Robot.population == 0: print('{0} был последним.'.format(self.name)) else: print('Осталось {0:d} работающих роботов.'.format(Robot.population)) def sayHi(self): '''Приветствие робота. Да, они это могут.''' print('Приветствую! Мои хозяева называют меня {0}.'.format(self.name)) def howMany(): '''Выводит численность роботов.''' print('У нас {0:d} роботов.'.format(Robot.population)) howMany = staticmethod(howMany) droid1 = Robot('R2-D2') droid1.sayHi() Robot.howMany() droid2 = Robot('C-3PO') droid2.sayHi() Robot.howMany() print("\nЗдесь роботы могут проделать какую-то работу.\n") print("Роботы закончили свою работу. Давайте уничтожим их.") del droid1 del droid2 Robot.howMany()
Вывод:
$ python3 objvar.py (Инициализация R2-D2) Приветствую! Мои хозяева называют меня R2-D2. У нас 1 роботов. (Инициализация C-3PO) Приветствую! Мои хозяева называют меня C-3PO. У нас 2 роботов. Здесь роботы могут проделать какую-то работу. Роботы закончили свою работу. Давайте уничтожим их. R2-D2 уничтожается! Осталось 1 работающих роботов. C-3PO уничтожается! C-3PO был последним. У нас 0 роботов.
Как это работает:
Это длинный пример, но он помогает продемонстрировать природу переменных
класса и объекта. Здесь population
принадлежит классу Robot
, и
поэтому является переменной класса. Переменная name
принадлежит объекту
(ей присваивается значение при помощи self
), и поэтому является
переменной объекта.
Таким образом, мы обращаемся к переменной класса population
как Robot.population
, а не self.population
. К переменной же объекта name
во всех методах этого объекта мы обращаемся при помощи обозначения self.name
. Помните об этой простой разнице между переменными класса и
объекта. Также имейте в виду, что переменная объекта с тем же именем, что и
переменная класса, сделает недоступной («спрячет») переменную класса!
Метод howMany
принадлежит классу, а не объекту. Это означает, что мы
можем определить его как classmethod
или staticmethod
, в зависимости
от того, нужно ли нам знать, в каком классе мы находимся. Поскольку нам
не нужна такая информация, мы воспользуемся staticmethod
.
Мы могли достичь того же самого, используя декораторы:
@staticmethod def howMany(): '''Выводит численность роботов.''' print('У нас {0:d} роботов.'.format(Robot.population))
Декораторы можно считать неким упрощённым способом вызова явного оператора, как мы видели в этом примере.
Пронаблюдайте, как метод __init__
используется для инициализации экземпляра Robot
с именем. В этом методе мы увеличиваем счётчик population
на 1,
так как добавляем ещё одного робота. Также заметьте, что значения self.name
для каждого объекта свои, что указывает на природу переменных объекта.
Помните, что к переменным и методам самого объекта нужно обращаться, пользуясь только self
. Это называется доступом к атрибутам.
В этом примере мы также наблюдали применение строк документации для классов,
равно как и для методов. Во время выполнения мы можем обращаться к строке
документации класса при помощи «Robot.__doc__
«, а к строке документации
метода — при помощи «Robot.sayHi.__doc__
«.
Наряду с методом __init__
, существует и другой специальный метод __del__
, который вызывается тогда, когда объект собирается умереть, т.е.
когда он больше не используется, и занимаемая им память возвращается
операционной системе для другого использования. В этом методе мы просто
уменьшаем счётчик Robot.population
на 1.
Метод __del__
запускается лишь тогда, когда объект перестаёт использоваться,
а поэтому заранее неизвестно, когда именно этот момент наступит. Чтобы
увидеть его в действии явно, придётся воспользоваться оператором del
, что
мы и сделали выше.
Примечание для программистов на C++/Java/C#
В Python все члены класса (включая данные) являются публичными (public), а все методы — виртуальными (virtual).
Исключение: Если имя переменной начинается с двойного подчёркивания, как,
например, __privatevar
, Python делает эту переменную приватной
(private).
Поэтому принято имя любой переменной, которая должна использоваться только внутри класса или объекта, начинать с подчёркивания; все же остальные имена являются публичными, и могут использоваться в других классах/объектах. Помните, что это лишь традиция, и Python вовсе не обязывает делать именно так (кроме двойного подчёркивания).
Наследование
Одно из главных достоинств объектно-ориентированного программирования заключается в многократном использовании одного и того же кода, и один из способов этого достичь — при помощи механизма наследования. Легче всего представить себе наследование в виде отношения между классами как тип и подтип.
Представим, что нам нужно написать программу, которая отслеживает информацию о преподавателях и студентах в колледже. У них есть некоторые общие характеристики: имя, возраст и адрес. Есть также и специфические характеристики, такие как зарплата, курсы и отпуск для преподавателей, а также оценки и оплата за обучение для студентов.
Можно создать для них независимые классы и работать с ними, но тогда добавление какой-либо новой общей характеристики потребует добавления её к каждому из этих независимых классов в отдельности, что делает программу неповоротливой.
Лучше создать общий класс с именем SchoolMember
, а затем сделать так, чтобы
классы преподавателя и студента наследовали этот класс, т.е. чтобы они стали
подтипами этого типа (класса), после чего добавить любые специфические
характеристики к этим подтипам.
У такого подхода есть множество достоинств. Если мы добавим/изменим какую-либо
функциональность в SchoolMember
, это автоматически отобразится и во всех
подтипах. Например, мы можем добавить новое поле удостоверения для
преподавателей и студентов, просто добавив его к классу SchoolMember
. С
другой стороны, изменения в подтипах никак не влияют на другие подтипы. Ещё одно
достоинство состоит в том, что обращаться к объекту преподавателя или студента
можно как к объекту SchoolMember
, что может быть полезно в ряде случаев,
например, для подсчёта количества человек в школе. Когда подтип может быть
подставлен в любом месте, где ожидается родительский тип, т.е. объект считается
экземпляром родительского класса, это называется полиморфизмом.
Заметьте также, что код родительского класса используется многократно, и нет необходимости копировать его во все классы, как пришлось бы в случае использования независимых классов.
Класс SchoolMember
в этой ситуации называют базовым классом или надклассом3. Классы Teacher
и Student
называют производными
классами или подклассами4.
Рассмотрим теперь этот пример в виде программы (сохраните как inherit.py
).
class SchoolMember: '''Представляет любого человека в школе.''' def __init__(self, name, age): self.name = name self.age = age print('(Создан SchoolMember: {0})'.format(self.name)) def tell(self): '''Вывести информацию.''' print('Имя:"{0}" Возраст:"{1}"'.format(self.name, self.age), end=" ") class Teacher(SchoolMember): '''Представляет преподавателя.''' def __init__(self, name, age, salary): SchoolMember.__init__(self, name, age) self.salary = salary print('(Создан Teacher: {0})'.format(self.name)) def tell(self): SchoolMember.tell(self) print('Зарплата: "{0:d}"'.format(self.salary)) class Student(SchoolMember): '''Представляет студента.''' def __init__(self, name, age, marks): SchoolMember.__init__(self, name, age) self.marks = marks print('(Создан Student: {0})'. format(self.name)) def tell(self): SchoolMember.tell(self) print('Оценки: "{0:d}"'.format(self.marks)) t = Teacher('Mrs. Shrividya', 40, 30000) s = Student('Swaroop', 25, 75) print() # печатает пустую строку members = [t, s] for member in members: member.tell() # работает как для преподавателя, так и для студента
Вывод:
$ python3 inherit.py (Создан SchoolMember: Mrs. Shrividya) (Создан Teacher: Mrs. Shrividya) (Создан SchoolMember: Swaroop) (Создан Student: Swaroop) Имя:"Mrs. Shrividya" Возраст:"40" Зарплата: "30000" Имя:"Swaroop" Возраст:"25" Оценки: "75"
Как это работает:
Чтобы воспользоваться наследованием, при определении класса мы указываем
имена его базовых классов в виде кортежа, следующего сразу за его именем.
Далее мы видим, что метод __init__
базового класса вызывается явно при
помощи переменной self
, чтобы инициализировать часть объекта,
относящуюся к базовому классу. Это очень важно запомнить: поскольку мы
определяем метод __init__
в подклассах Teacher
и Student
, Python
не вызывает конструктор базового класса SchoolMember
автоматически —
его необходимо вызывать самостоятельно в явном виде.
Напротив, если мы не определим метод __init__
в подклассе, Python
вызовет конструктор базового класса автоматически.
Здесь же мы видим, как можно вызывать методы базового класса, предваряя
запись имени метода именем класса, а затем передавая переменную self
вместе с другими аргументами.
Обратите внимание, что при вызове метода tell
из класса SchoolMember
экземпляры Teacher
или Student
можно использовать как экземпляры SchoolMember
.
Заметьте также, что вызывается метод tell
из подкласса, а не метод tell
из класса SchoolMember
. Это можно понять следующим образом:
Python всегда начинает поиск методов в самом классе, что он и делает в
данном случае. Если же он не находит метода, он начинает искать методы,
принадлежащие базовым классам по очереди, в порядке, в котором они
перечислены в кортеже при определении класса.
Замечание по терминологии: если при наследовании перечислено более одного класса, это называется множественным наследованием.
Параметр end
используется в методе tell()
для того, чтобы новая
строка начиналась через пробел после вызова print()
.
Метаклассы
В обширной теме объектно-ориентированного программирования существует ещё много всего, но мы лишь слегка коснёмся некоторых концепций, чтобы вы просто знали об их существовании.
Точно так же, как классы используются для создания объектов, можно использовать метаклассы5 для создания классов. Метаклассы существуют для изменения или добавления нового поведения в классы.
Давайте рассмотрим пример. Допустим, мы хотим быть уверены, что мы всегда
создаём исключительно экземпляры подклассов класса SchoolMember
, и не
создаём экземпляры самого класса SchoolMember
.
Для достижения этой цели мы можем использовать концепцию под названием «абстрактные базовые классы». Это означает, что такой класс абстрактен, т. е. является лишь некой концепцией, не предназначенной для использования в качестве реального класса.
Мы можем объявить наш класс как абстрактный базовый класс при помощи
встроенного метакласса по имени ABCMeta
.
#!/usr/bin/env python # Filename: inherit_abc.py from abc import * class SchoolMember(metaclass=ABCMeta): '''Представляет любого человека в школе.''' def __init__(self, name, age): self.name = name self.age = age print('(Создан SchoolMember: {0})'.format(self.name)) @abstractmethod def tell(self): '''Вывести информацию.''' print('Имя:"{0}" Возраст:"{1}"'.format(self.name, self.age), end=" ") class Teacher(SchoolMember): '''Представляет преподавателя.''' def __init__(self, name, age, salary): SchoolMember.__init__(self, name, age) self. salary = salary print('(Создан Teacher: {0})'.format(self.name)) def tell(self): SchoolMember.tell(self) print('Зарплата: "{0:d}"'.format(self.salary)) class Student(SchoolMember): '''Представляет студента.''' def __init__(self, name, age, marks): SchoolMember.__init__(self, name, age) self.marks = marks print('(Создан Student: {0})'.format(self.name)) def tell(self): SchoolMember.tell(self) print('Оценки: "{0:d}"'.format(self.marks)) t = Teacher('Mrs. Shrividya', 40, 30000) s = Student('Swaroop', 25, 75) #m = SchoolMember('abc', 10) # Это приведёт к ошибке: "TypeError: Can't instantiate abstract class # SchoolMember with abstract methods tell" print() # печатает пустую строку members = [t, s] for member in members: member.tell() # работает как для преподавателя, так и для студента
Вывод:
$ python3 inherit.py (Создан SchoolMember: Mrs. Shrividya) (Создан Teacher: Mrs. Shrividya) (Создан SchoolMember: Swaroop) (Создан Student: Swaroop) Имя:"Mrs. Shrividya" Возраст:"40" Зарплата: "30000" Имя:"Swaroop" Возраст:"25" Оценки: "75"
Как это работает:
Мы можем объявить метод tell
класса SchoolMember
абстрактным, и таким
образом автоматически запретим создавать экземпляры класса SchoolMember
.
Тем не менее, мы можем работать с экземплярами Teacher
и Student
так,
как будто они экземпляры SchoolMember
, поскольку они являются подклассами.
Резюме
Мы изучили различные аспекты классов и объектов, равно как и терминологию, связанную с ними. Мы также увидели ряд достоинств и «подводных камней» объектно-ориентированного программирования. Python — в высокой степени объектно-ориентирован, поэтому понимание этих принципов очень поможет вам в дальнейшем.
Далее мы узнаем, как работать с вводом/выводом и получать доступ к файлам в Python.
boxing and unboxing ↩
self — англ. «сам» (прим.перев.) ↩
также «суперкласс», «родительский класс» (прим.перев.) ↩
также «субкласс», «класс-наследник» (прим.перев.) ↩
в оригинальной версии книги этот параграф невидим для читателей, так как находится в комментарии с пометкой автора «It is too sudden to introduce this concept here.», что означает «Слишком неожиданно представление этой концепции здесь.» (прим.перев.) ↩
Объектно-ориентированное программирование на Python
Python — фантастический язык программирования, который позволяет использовать как функциональные, так и объектно-ориентированные парадигмы программирования.
Программисты на Python должны иметь возможность использовать фундаментальные концепции объектно-ориентированного программирования, будь то разработчики программного обеспечения, инженеры по машинному обучению или кто-то еще.
Все четыре основных аспекта общей структуры ООП поддерживаются системой объектно-ориентированного программирования Python: инкапсуляция, абстракция, наследование и полиморфизм.
В этом уроке мы кратко рассмотрим эти функции и попрактикуемся с ними.
Концепции объектно-ориентированного программирования в Python
Что такое классы и объекты?
Python, как и любой другой объектно-ориентированный язык, позволяет вам определять классы для создания объектов. Встроенные классы Python — это наиболее распространенные типы данных в Python, такие как строки, списки, словари и т. д.
Класс — это набор переменных экземпляра и связанных методов, определяющих конкретный тип объекта. Вы можете думать о классе как о чертеже или шаблоне объекта. Атрибуты — это имена, данные переменным, которые составляют класс.
Экземпляр класса с определенным набором свойств называется объектом. В результате один и тот же класс можно использовать для создания любого количества объектов.
Давайте определим класс с именем Book для программы продаж книг.
class Book:
def __init__(self, title, quantity, author, price):
self. title = title
self.quantity = quantity
self.author = author
self.price = price
Специальный метод __init__, также известный как конструктор, используется для инициализации класса Book с такими атрибутами, как название, количество, автор и цена.
В Python встроенные классы именуются строчными буквами, а пользовательские классы именуются в Camel case или Snake case с заглавной первой буквой.
Этот класс может быть создан для любого количества объектов. В следующем примере кода создаются экземпляры трех книг:
book1 = Book('Book 1', 12, 'Author 1', 120)
book2 = Book('Book 2', 18, 'Author 2', 220)
book3 = Book('Book 3', 28, 'Author 3', 320)
book1, book2 и book3 — разные объекты класса Book. Термин self в атрибутах относится к соответствующим экземплярам (объектам).
print(book1)
print(book2)
print(book3)
Выход:
<__main__. Book object at 0x00000156EE59A9D0>
<__main__.Book object at 0x00000156EE59A8B0>
<__main__.Book object at 0x00000156EE59ADF0>
Класс и место в памяти объектов печатаются при их печати (вызове функции print). Мы не можем ожидать, что они предоставят конкретную информацию о свойствах объекта, таких как название, имя автора и так далее.
Но для этого мы можем использовать специальный метод __repr__.
В Python специальный метод — это определенная функция, которая начинается и заканчивается двумя знаками подчеркивания и вызывается автоматически при выполнении определенных условий.
class Book:
def __init__(self, title, quantity, author, price):
self.title = title
self.quantity = quantity
self.author = author
self.price = price
def __repr__(self):
return f"Book: {self.title}, Quantity: {self.quantity}, Author: {self.author}, Price: {self. price}"
book1 = Book('Book 1', 12, 'Author 1', 120)
book2 = Book('Book 2', 18, 'Author 2', 220)
book3 = Book('Book 3', 28, 'Author 3', 320)
print(book1)
print(book2)
print(book3)
Выход:
Book: Book 1, Quantity: 12, Author: Author 1, Price: 120
Book: Book 2, Quantity: 18, Author: Author 2, Price: 220
Book: Book 3, Quantity: 28, Author: Author 3, Price: 320
Что такое инкапсуляция?
Инкапсуляция — это процесс предотвращения доступа клиентов к определенным свойствам, доступ к которым можно получить только с помощью определенных методов.
Приватные атрибуты — это недоступные атрибуты, а сокрытие информации — это процесс превращения определенных атрибутов в приватные. Вы используете два символа подчеркивания для объявления частных характеристик.
Давайте введем закрытый атрибут с именем __discount в классе Book.
class Book:
def __init__(self, title, quantity, author, price):
self. title = title
self.quantity = quantity
self.author = author
self.price = price
self.__discount = 0.10
def __repr__(self):
return f"Book: {self.title}, Quantity: {self.quantity}, Author: {self.author}, Price: {self.price}"
book1 = Book('Book 1', 12, 'Author 1', 120)
print(book1.title)
print(book1.quantity)
print(book1.author)
print(book1.price)
print(book1.__discount)
Book 1
12
Author 1
120
Traceback (most recent call last):
File "C:\Users\ashut\Desktop\Test\hello\test.py", line 19, in
print(book1.__discount)
AttributeError: 'Book' object has no attribute '__discount'
Мы видим, что печатаются все атрибуты, кроме приватного атрибута __discount. Для получения доступа к таким свойствам обычно создаются дополнительные методы
В следующем примере кода мы сделаем свойство price приватным и создадим дополнительный метод для установки для присвоения скидки и функцию получения для получения цены.
class Book:
def __init__(self, title, quantity, author, price):
self.title = title
self.quantity = quantity
self.author = author
self.__price = price
self.__discount = None
def set_discount(self, discount):
self.__discount = discount
def get_price(self):
if self.__discount:
return self.__price * (1-self.__discount)
return self.__price
def __repr__(self):
return f"Book: {self.title}, Quantity: {self.quantity}, Author: {self.author}, Price: {self.get_price()}"
На этот раз мы создадим два объекта: один для покупки одной книги, а другой для покупки книг в большом количестве. При покупке книг оптом мы получаем скидку 20%, поэтому в этом случае мы будем использовать метод set_discount(), чтобы установить скидку 20%.
single_book = Book('Two States', 1, 'Chetan Bhagat', 200)
bulk_books = Book('Two States', 25, 'Chetan Bhagat', 200)
bulk_books. set_discount(0.20)
print(single_book.get_price())
print(bulk_books.get_price())
print(single_book)
print(bulk_books)
Выход:
200
160.0
Book: Two States, Quantity: 1, Author: Chetan Bhagat, Price: 200
Book: Two States, Quantity: 25, Author: Chetan Bhagat, Price: 160.0
Что такое наследование в Python?
Наследование считается наиболее важной характеристикой ООП. Способность класса наследовать методы и/или характеристики от другого класса называется наследованием.
Подкласс или дочерний класс — это класс, который наследуется. Суперкласс или родительский класс — это класс, от которого наследуются методы и/или атрибуты.
В программное обеспечение для продаж нашего книготорговца добавлены два новых класса: Novel класс и Academic класс.
Мы видим, что независимо от того, классифицируется ли книга как новела или академическая, она может иметь некоторые схожие атрибуты, такие как название и автор, а также общие методы, такие как get_price() и set_discount(). Переписывать весь этот код для каждого нового класса — пустая трата времени, усилий и памяти.
class Book:
def __init__(self, title, quantity, author, price):
self.title = title
self.quantity = quantity
self.author = author
self.__price = price
self.__discount = None
def set_discount(self, discount):
self.__discount = discount
def get_price(self):
if self.__discount:
return self.__price * (1-self.__discount)
return self.__price
def __repr__(self):
return f"Book: {self.title}, Quantity: {self.quantity}, Author: {self.author}, Price: {self.get_price()}"
class Novel(Book):
def __init__(self, title, quantity, author, price, pages):
super().__init__(title, quantity, author, price)
self.pages = pages
class Academic(Book):
def __init__(self, title, quantity, author, price, branch):
super(). __init__(title, quantity, author, price)
self.branch = branch
Давайте создадим объекты для этих классов, чтобы визуализировать их.
novel1 = Novel('Two States', 20, 'Chetan Bhagat', 200, 187)
novel1.set_discount(0.20)
academic1 = Academic('Python Foundations', 12, 'PSF', 655, 'IT')
print(novel1)
print(academic1)
Вывод:
Book: Two States, Quantity: 20, Author: Chetan Bhagat, Price: 160.0
Book: Python Foundations, Quantity: 12, Author: PSF, Price: 655
Что такое Полиморфизм?
Термин «полиморфизм» происходит из греческого языка и означает «нечто, что принимает несколько форм».
Полиморфизм относится к способности подкласса адаптировать метод, который уже существует в его суперклассе, для удовлетворения своих потребностей. Другими словами, подкласс может использовать метод своего суперкласса как есть или модифицировать его по мере необходимости.
class Academic(Book):
def __init__(self, title, quantity, author, price, branch):
super(). __init__(title, quantity, author, price)
self.branch = branch
def __repr__(self):
return f"Book: {self.title}, Branch: {self.branch}, Quantity: {self.quantity}, Author: {self.author}, Price: {self.get_price()}"
Суперкласс Book имеет специальный метод, который называется __repr__. Этот метод может использоваться подклассом Novel, чтобы он вызывался всякий раз, когда объект печатается.
С другой стороны, подкласс Academic определяется собственной специальной метод __repr__ в приведенном выше примере кода. Подкласс Academic будет вызывать свой собственный метод, подавляя тот же метод, что и в его суперклассе, благодаря полиморфизму.
novel1 = Novel('Two States', 20, 'Chetan Bhagat', 200, 187)
novel1.set_discount(0.20)
academic1 = Academic('Python Foundations', 12, 'PSF', 655, 'IT')
print(novel1)
print(academic1)
Вывод:
Book: Two States, Quantity: 20, Author: Chetan Bhagat, Price: 160. 0
Book: Python Foundations, Branch: IT, Quantity: 12, Author: PSF, Price: 655
Что такое Абстракция?
Абстракция не поддерживается напрямую в Python. С другой стороны, вызов магического метода допускает абстракцию.
Если абстрактный метод объявлен в суперклассе, подклассы, наследуемые от суперкласса, должны иметь собственную реализацию этого метода.
Абстрактный метод суперкласса никогда не будет вызываться его подклассами. Но абстракция помогает поддерживать одинаковую структуру во всех подклассах.
В нашем родительском классе Book мы определили метод __repr__. Давайте сделаем этот метод абстрактным, заставив каждый подкласс обязательно иметь свой собственный метод __repr__.
from abc import ABC, abstractmethod
class Book(ABC):
def __init__(self, title, quantity, author, price):
self.title = title
self.quantity = quantity
self.author = author
self.__price = price
self. __discount = None
def set_discount(self, discount):
self.__discount = discount
def get_price(self):
if self.__discount:
return self.__price * (1-self.__discount)
return self.__price
@abstractmethod
def __repr__(self):
return f"Book: {self.title}, Quantity: {self.quantity}, Author: {self.author}, Price: {self.get_price()}"
class Novel(Book):
def __init__(self, title, quantity, author, price, pages):
super().__init__(title, quantity, author, price)
self.pages = pages
class Academic(Book):
def __init__(self, title, quantity, author, price, branch):
super().__init__(title, quantity, author, price)
self.branch = branch
def __repr__(self):
return f"Book: {self.title}, Branch: {self.branch}, Quantity: {self.quantity}, Author: {self.author}, Price: {self. get_price()}"
novel1 = Novel('Two States', 20, 'Chetan Bhagat', 200, 187)
novel1.set_discount(0.20)
academic1 = Academic('Python Foundations', 12, 'PSF', 655, 'IT')
print(novel1)
print(academic1)
В приведенном выше коде мы унаследовали класс ABC для создания класса Book. Мы сделали метод __repr__ абстрактным, добавив декоратор @abstractmethod.
При создании класса Novel мы намеренно пропустили реализацию метода __repr__, чтобы посмотреть, что получится:
Traceback (most recent call last):
File "C:\Users\ashut\Desktop\Test\hello\test.py", line 40, in
novel1 = Novel('Two States', 20, 'Chetan Bhagat', 200, 187)
TypeError: Can't instantiate abstract class Novel with abstract method __repr__
Мы получаем TypeError о том, что мы не можем создать экземпляр объекта класса Novel. Давайте добавим реализацию метода __repr__ и посмотрим, что теперь получится.
class Novel(Book):
def __init__(self, title, quantity, author, price, pages):
super().__init__(title, quantity, author, price)
self.pages = pages
def __repr__(self):
return f"Book: {self.title}, Quantity: {self.quantity}, Author: {self.author}, Price: {self.get_price()}"
Вывод:
Book: Two States, Quantity: 20, Author: Chetan Bhagat, Price: 160.0
Book: Python Foundations, Branch: IT, Quantity: 12, Author: PSF, Price: 655
Теперь он работает нормально.
Перегрузка метода
Концепция перегрузки методов встречается почти во всех известных языках программирования, которые следуют концепциям объектно-ориентированного программирования. Это просто относится к использованию множества методов с одним и тем же именем, которые принимают различное количество аргументов в пределах одного класса.
Давайте теперь разберемся с перегрузкой методов с помощью следующего кода:
class OverloadingDemo:
def add(self, x, y):
print(x+y)
def add(self, x, y, z):
print(x+y+z)
obj = OverloadingDemo()
obj. add(2, 3)
Вывод:
Traceback (most recent call last):
File "C:\Users\ashut\Desktop\Test\hello\setup.py", line 10, in <module>
obj.add(2, 3)
TypeError: add() missing 1 required positional argument: 'z'
Вам, наверное, интересно, почему это произошло. В результате отображается ошибка, поскольку Python запоминает только самое последнее определение метода add(self, x, y, z) который принимает три параметра в дополнение к self. В результате при вызове метода add() необходимо передать три аргумента. Другими словами, он игнорирует предыдущее определение add().
Таким образом, Python по умолчанию не поддерживает перегрузку методов.
Переопределение метода
Когда метод с тем же именем и аргументами используется как в производном классе, так и в базовом или суперклассе, мы говорим, что метод производного класса «переопределяет» метод, предоставленный в базовом классе.
При вызове переопределенного метода всегда вызывается метод производного класса. Метод, который использовался в базовом классе, теперь скрыт.
class ParentClass:
def call_me(self):
print("I am parent class")
class ChildClass(ParentClass):
def call_me(self):
print("I am child class")
pobj = ParentClass()
pobj.call_me()
cobj = ChildClass()
cobj.call_me()
Вывод:
I am parent class
I am child class
В приведенном выше коде, когда метод call_me() вызывается для дочернего объекта, вызывается call_me() из дочернего класса. Мы можем вызвать метод call_me() родительского класса из дочернего класса, используя super(), например так:
class ParentClass:
def call_me(self):
print("I am parent class")
class ChildClass(ParentClass):
def call_me(self):
print("I am child class")
super().call_me()
pobj = ParentClass()
pobj. call_me()
cobj = ChildClass()
cobj.call_me()
Вывод:
I am parent class
I am child class
I am parent class
ООП-упражнение Python — упражнения с классами и объектами
Это упражнение по объектно-ориентированному программированию (ООП) призвано помочь вам изучить и применить на практике концепции ООП. Все вопросы тестируются на Python 3.
Python Объектно-ориентированное программирование (ООП) основано на концепции «объектов», которые могут содержать данные и код: данные в форме переменных экземпляра (часто называемых атрибутами или свойствами) и код в методе формы. То есть, используя ООП, мы инкапсулируем связанные свойства и поведение в отдельные объекты.
Что включено в это упражнение Python OOP?
Это упражнение по классам и объектам ООП включает в себя 8 различных программ, вопросов и задач. Все решения протестированы на Python 3.
В этом упражнении ООП рассматриваются вопросы по следующим темам :
- Создание классов и объектов
- Переменные экземпляра и методы, а также атрибуты уровня класса
- Системы моделей с наследованием классов, т. е. Из других классов
- Родительские классы и дочерние классы
- Расширение функциональности родительских классов с помощью дочернего класса
- Проверка объектов
Когда вы отвечаете на каждый вопрос, вы лучше знакомитесь с ООП Python. Дайте нам знать, если у вас есть альтернативные решения . Это поможет другим разработчикам.
Используйте онлайн-редактор кода для решения упражнений.
- ООП Упражнение 1. Создание класса с атрибутами экземпляра
- ООП Упражнение 2: Создайте класс Vehicle без каких-либо переменных и методов
- ООП Упражнение 3: Создайте дочерний класс Bus, который наследует все переменные и методы класса Vehicle
- ООП Упражнение 4: Наследование классов
- ООП Упражнение 5: Определите свойство, которое должно иметь одинаковое значение для каждого экземпляра класса (объекта)
- ООП Упражнение 6: Наследование класса
- ООП Упражнение 7: Проверка типа объекта
- ООП Упражнение 8: Определите, является ли School_bus также экземпляром класса ТС
ООП Упражнение 1.
Создание класса с атрибутами экземпляра Напишите программу на Python для создания класса транспортного средства с атрибутами экземпляра max_speed
и mileage
.
См. :
- Классы и объекты в Python
- Переменные экземпляра в Python
класс Транспортное средство: def __init__(я, максимальная_скорость, пробег): self.max_speed = максимальная_скорость self.mileage = пробег модельX = Транспортное средство (240, 18) print(modelX.max_speed, modelX.mileage)
ООП Упражнение 2: Создайте класс Vehicle без каких-либо переменных и методов
Показать решениеclass Vehicle: pass
ООП Упражнение 3: Создайте дочерний класс Bus, который унаследует все переменные и методы класса Vehicle
Учитывая :
класс Vehicle: def __init__(self, name, max_speed, mileage): self.name = имя self. max_speed = максимальная_скорость self.mileage = mileage
Создайте объект Bus, который унаследует все переменные и методы родительского класса Vehicle и отобразит его.
Ожидаемый результат:
Название автомобиля: School Volvo Скорость: 180 Пробег: 12
Обратитесь : Наследование в Python def __init__(self, name, max_speed, mileage): self.name = имя self.max_speed = максимальная_скорость self.mileage = пробег Класс Автобус(Автомобиль): проходить School_bus = Bus(«Школьный Volvo», 180, 12) print(«Название транспортного средства:», School_bus.name, «Скорость:», School_bus.max_speed, «Пробег:», School_bus.mileage)
ООП Упражнение 4: Наследование классов
Дано :
Создайте класс Bus , который наследуется от класса Vehicle . Присвойте аргументу емкости Bus.seating_capacity()
значение по умолчанию , равное 50.
Используйте следующий код для родительского класса транспортного средства.
класс Транспортное средство: def __init__(self, name, max_speed, mileage): self.name = имя self.max_speed = максимальная_скорость self.mileage = пробег def seat_capacity (я, вместимость): return f"Вместимость {self.name} равна {вместимости} пассажиров"
Ожидаемая выход :
Сидения автобуса составляет 50 пассажиров
См. :
- Наследование в Python
- Polymorphism в Python
- .
- Затем используйте аргумент метода по умолчанию в определении метода
seating_capacity()
класса шины.
Транспортное средство: def __init__(self, name, max_speed, mileage): self.name = имя self.max_speed = максимальная_скорость self.mileage = пробег def seat_capacity (я, вместимость): return f"Вместимость {self. name} равна {вместимости} пассажиров" Класс Автобус(Автомобиль): # присвоить значение емкости по умолчанию def seat_capacity (я, вместимость = 50): вернуть super().seating_capacity(capacity=50) School_bus = Bus("Школьный Volvo", 180, 12) печать (School_bus.посадочные места ())
ООП Упражнение 5. Определите свойство, которое должно иметь одинаковое значение для каждого экземпляра класса (объекта). То есть, каждое транспортное средство должно быть белым.
В этом упражнении используйте следующий код.
класс Транспортное средство: def __init__(self, name, max_speed, mileage): self.name = имя self.max_speed = максимальная_скорость self.mileage = пробег Класс Автобус(Автомобиль): проходить Класс Автомобиль(Автомобиль): пройти
Ожидаемый результат :
Цвет: Белый, Название автомобиля: School Volvo, Скорость: 180, Пробег: 12 Цвет: Белый, Название транспортного средства: Audi Q5, Скорость: 240, Пробег: 18
Обратитесь : Переменная класса в Python
Показать подсказку Определите цвет
как переменную класса в классе транспортного средства
Созданные переменные в . __init__()
называются переменными экземпляра. Значение переменной экземпляра зависит от конкретного экземпляра класса. Например, в решении все объекты Vehicle имеют имя и max_speed, но значения переменных name и max_speed будут различаться в зависимости от экземпляра Vehicle.
С другой стороны, переменная класса является общей для всех экземпляров класса s. Вы можете определить атрибут класса, присвоив значение имени переменной вне .__init__()
.
класс Транспортное средство: # Атрибут класса цвет = "Белый" def __init__(self, name, max_speed, mileage): self.name = имя self.max_speed = максимальная_скорость self.mileage = пробег Класс Автобус(Автомобиль): проходить Класс Автомобиль(Автомобиль): проходить School_bus = Bus("Школьный Volvo", 180, 12) print(School_bus.color, School_bus.name, "Скорость:", School_bus.max_speed, "Пробег:", School_bus.mileage) автомобиль = Автомобиль ("Ауди Q5", 240, 18) print(car. color, car.name, "Скорость:", car.max_speed, "Пробег:", car.mileage)
ООП Упражнение 6: Наследование классов
Дано :
Создайте дочерний класс Bus , который наследуется от класса Vehicle. Стоимость проезда по умолчанию для любого транспортного средства составляет мест * 100 . Если транспортное средство является экземпляром автобуса , нам нужно добавить дополнительные 10% к полной стоимости проезда в качестве платы за обслуживание. Таким образом, общая стоимость проезда для экземпляра автобуса станет окончательной суммой = общая стоимость проезда + 10% от общей стоимости проезда.
Примечание: Автобус рассчитан на 9 мест.0007 50 . поэтому окончательная сумма тарифа должна быть 5500. Вам необходимо переопределить метод fare()
класса транспортного средства в классе автобуса.
Используйте следующий код для родительского класса транспортных средств. Нам нужно получить доступ к родительскому классу из метода дочернего класса.
класс Транспортное средство: def __init__(я, имя, пробег, вместимость): self.name = имя self.mileage = пробег собственная мощность = мощность деф тариф (сам): возврат собственной емкости * 100 Класс Автобус(Автомобиль): проходить School_bus = Автобус("Школьный Volvo", 12, 50) print("Общая стоимость проезда на автобусе:", School_bus.fare())
Ожидаемый результат :
Общая стоимость проезда на автобусе: 5500,0Показать решение
класс Транспортное средство: def __init__(я, имя, пробег, вместимость): self.name = имя self.mileage = пробег собственная мощность = мощность деф тариф (сам): возврат собственной емкости * 100 Класс Автобус(Автомобиль): деф тариф (сам): сумма = супер().тариф() сумма += сумма * 10/100 сумма возврата School_bus = Автобус("Школьный Volvo", 12, 50) print("Общая стоимость проезда на автобусе:", School_bus. fare())
ООП Упражнение 7. Проверка типа объекта
Напишите программу для определения, к какому классу принадлежит данный объект Bus.
Дано :
класс Транспортное средство: def __init__(я, имя, пробег, вместимость): self.name = имя self.mileage = пробег собственная мощность = мощность Класс Автобус(Автомобиль): проходить School_bus = Bus("School Volvo", 12, 50)Показать подсказку
Использовать встроенную функцию Python type()
.
класс Транспортное средство: def __init__(я, имя, пробег, вместимость): self.name = имя self.mileage = пробег собственная мощность = мощность Класс Автобус(Автомобиль): проходить School_bus = Автобус("Школьный Volvo", 12, 50) # Встроенный тип Python() print(type(School_bus))
ООП Упражнение 8. Определите, является ли School_bus также экземпляром класса Vehicle
Учитывая :
class Vehicle: def __init__(я, имя, пробег, вместимость): self. name = имя self.mileage = пробег собственная мощность = мощность Класс Автобус(Автомобиль): проходить School_bus = Bus("Школьный Volvo", 12, 50)Show Solution
класс Транспортное средство: def __init__(я, имя, пробег, вместимость): self.name = имя self.mileage = пробег собственная мощность = мощность Класс Автобус(Автомобиль): проходить School_bus = Автобус("Школьный Volvo", 12, 50) # Встроенная функция Python isinstance() print(isinstance(School_bus, Vehicle))
Цифровой кот — объектно-ориентированное программирование на Python 3
Этот пост доступен как блокнот IPython здесь
Об этой серии
Объектно-ориентированное программирование (ООП) было ведущей парадигмой программирования в течение нескольких десятилетий, начиная с первых попыток в 60-х годах и заканчивая некоторыми из наиболее важных языков, используемых в настоящее время. Будучи набором концепций программирования и методологий проектирования, ООП никогда нельзя назвать «правильно» или «полностью» реализованным в языке: действительно, существует столько же реализаций, сколько и языков.
Итак, один из самых интересных аспектов языков ООП — понять, как они реализуют эти концепции. В этом посте я попытаюсь начать анализ ООП-реализации языка Python. Однако, из-за богатства темы, я рассматриваю эту попытку как набор мыслей для начинающих Python, пытающихся найти свой путь в этом прекрасном (и иногда необычном) языке.
В этой серии статей мы хотим познакомить читателя с реализацией концепций объектно-ориентированного программирования в Python 3. Однако содержание этой и следующих статей не будет полностью отличаться от содержания предыдущей серии «Концепции ООП в Python 2.x». Причина в том, что, хотя некоторые внутренние структуры сильно меняются, глобальная философия остается неизменной, поскольку Python 3 является эволюцией Python 2, а не новым языком.
Поэтому я решил разделить предыдущую серию и адаптировать содержание к Python 3 вместо публикации простого списка исправлений. Я нахожу этот способ более полезным для новых читателей, которым в противном случае пришлось бы читать предыдущую серию.
Одним из наиболее заметных изменений, внесенных в Python 3, является преобразование ключевого слова print
в функцию print()
. Это действительно очень небольшое изменение по сравнению с другими изменениями, внесенными во внутренние структуры, но оно является наиболее заметным и будет источником 80% ваших синтаксических ошибок, когда вы начнете писать код Python 3.
Помните, что print теперь является функцией, поэтому пишите print(a)
, а не напечатать
.
Вернуться к объекту
Информатика имеет дело с данными и процедурами для обработки этих данных. Все, от самых ранних программ Fortran до новейших мобильных приложений, связано с данными и их манипулированием.
Итак, если данные — это ингредиенты, а процедуры — это рецепты, кажется (и может быть) разумным хранить их отдельно.
Давайте займемся процедурным программированием на Python
# Это некоторые данные данные = (13, 63, 5, 378, 58, 40) # Это процедура, которая вычисляет среднее среднее значение (d): возвращаемая сумма (d)/len (d) печать (среднее (данные))
Как видите, код довольно хороший и общий: процедура (функция) работает с последовательностью данных и возвращает среднее значение элементов последовательности. Пока все хорошо: вычисление среднего значения некоторых чисел оставляет числа нетронутыми и создает новые данные.
Наблюдение за повседневным миром, однако, показывает, что сложные данные мутируют : электрическое устройство включено или выключено, дверь открыта или закрыта, содержимое книжной полки в вашей комнате меняется, когда вы покупаете новые книги.
Вы по-прежнему можете управлять им, разделяя данные и процедуры, например
# Это две пронумерованные двери, изначально закрытые дверь1 = [1, 'закрыта'] дверь2 = [2, 'закрыто'] # Эта процедура открывает дверь защита open_door (дверь): дверь[1] = 'открыто' open_door (дверь1) печать (дверь1)
Я описал дверь как структуру, содержащую номер и статус двери (как вы могли бы сделать, например, в таких языках, как LISP). Процедура знает, как устроена эта структура, и может изменить ее.
Это тоже прекрасно работает. Однако некоторые проблемы возникают, когда мы начинаем создавать специализированные типы данных. Что происходит, например, когда я ввожу тип данных «запираемая дверь», которую можно открыть только тогда, когда она не заперта? Посмотрим
# Это две стандартные двери, изначально закрытые дверь1 = [1, 'закрыта'] дверь2 = [2, 'закрыто'] # Это запираемая дверь, изначально закрытая и разблокированная ldoor1 = [1, 'закрыто', 'разблокировано'] # Эта процедура открывает стандартную дверь защита open_door (дверь): дверь[1] = 'открыто' # Эта процедура открывает запираемую дверь def open_ldoor (дверь): если дверь[2] == 'разблокирована': дверь[1] = 'открыто' open_door (дверь1) печать (дверь1) open_ldoor(ldoor1) печать (ldoor1)
Все работает, никаких сюрпризов в этом коде. Однако, как видите, мне пришлось искать другое название для процедуры, открывающей запертую дверь, так как ее реализация отличается от процедуры, открывающей стандартную дверь. Но, подождите… Я все еще открываю дверь, действие то же самое, и оно просто меняет статус самой двери. Так почему я должен помнить, что запертая дверь должна быть открыта с помощью open_ldoor()
вместо open_door()
, если глагол тот же?
Скорее всего, это разделение между данными и процедурами не совсем подходит для некоторых ситуаций. Ключевая проблема заключается в том, что действие «открыть» на самом деле не с использованием двери; скорее это , меняющий свое состояние . Таким образом, точно так же, как кнопки управления громкостью вашего телефона, которые имеют номер на вашем телефоне , процедура «открытия» должна придерживаться данных «двери».
Именно это приводит к понятию объекта : объект в контексте ООП представляет собой структуру, содержащую данные 9Процедуры 0263 и , действующие на них.
Что насчет типа?
Когда вы говорите о данных сразу нужно ввести понятие типа . Это понятие может иметь два значения, которые стоит упомянуть в информатике: поведенческое и структурное .
Поведенческое значение представляет собой тот факт, что вы знаете, что что-то такое, описывая, как оно действует. Это основа так называемого «утиного набора» (здесь «печатать» означает «дать тип», а не «печатать на клавиатуре»): если это типа действует как утка, это это утка.
Структурное значение определяет тип чего-либо, глядя на его внутреннюю структуру. Таким образом, две вещи, которые действуют одинаково, но внутренне различны, относятся к разным типам.
Обе точки зрения могут быть правильными, и разные языки могут реализовывать и подчеркивать то или иное значение типа или даже оба.
Классовые игры
Объекты в Python могут быть созданы с описанием их структуры через класс . Класс — это программное представление универсального объекта, такого как «книга», «автомобиль», «дверь»: когда я говорю о «двери», каждый может понять, о чем я говорю, без необходимости ссылаться к определенной двери в комнате.
В Python тип объекта представлен классом, используемым для создания объекта: то есть в Python слово тип имеет то же значение, что и слово class .
Например, один из встроенных классов Python — int
, представляющее целое число
>>> a = 6 >>> напечатать(а) 6 >>> печать (тип (а)) <класс 'целое число'> >>> печать (a. __class__) <класс 'целое число'>
Как видите, встроенная функция type()
возвращает содержимое атрибута magic __class__
(магия здесь означает, что его значением управляет сам Python вне сцены). Тип переменной a
или ее класс: int
. (Это очень неточное описание этой довольно сложной темы, так что помните, что на данный момент мы только царапаем поверхность).
Если у вас есть класс, вы можете создать его экземпляр, чтобы получить конкретный объект (экземпляр ) этого типа, то есть объект, построенный в соответствии со структурой этого класса. Синтаксис Python для создания экземпляра класса такой же, как и у вызова функции
>>> b = int() >>> тип(б) <класс 'целое число'>
Когда вы создаете экземпляр, вы можете передать некоторые значения в соответствии с определением класса, чтобы инициализировать его.
>>> б = целое() >>> печатать(б) 0 >>> с = целое (7) >>> печатать(с) 7
В этом примере класс int
создает целое число со значением 0 при вызове без аргументов, в противном случае он использует данный аргумент для инициализации вновь созданного объекта.
Давайте напишем класс, который представляет дверь, чтобы соответствовать процедурным примерам, сделанным в первом разделе
класс Дверь: def __init__(я, номер, статус): self.number = число self.status = статус деф открыть (самостоятельно): self.status = 'открыто' защита близко (я): self.status = 'закрыто'
Ключевое слово class
определяет новый класс с именем Door
; все, что находится под классом
, является частью класса. Функции, которые вы пишете внутри объекта, называются методами и ничем не отличаются от стандартных функций; номенклатура меняется только для того, чтобы подчеркнуть тот факт, что эти функции теперь являются частью объекта.
Методы класса должны принимать в качестве первого аргумента специальное значение, называемое self
(имя является условным, но никогда не нарушайте его).
Классу может быть предоставлен специальный метод с именем __init__()
, который запускается при создании экземпляра класса, получая аргументы, переданные при вызове класса; общее название такого метода в контексте ООП — конструктор , даже если метод __init__()
не является единственной частью этого механизма в Python.
Переменные self.number
и self.status
называются атрибутами объекта. В Python методы и атрибуты являются 90 263 членами 90 264 объекта и доступны с точечным синтаксисом; разница между атрибутами и методами заключается в том, что последние могут быть вызваны (на жаргоне Python вы говорите, что метод — это callable ).
Как видите, метод __init__()
должен создавать и инициализировать атрибуты, поскольку они нигде больше не объявлены. Это очень важно в Python и строго связано с тем, как язык обрабатывает типы переменных. Я подробно расскажу об этих концепциях при работе с полиморфизмом в следующем посте.
Класс можно использовать для создания конкретного объекта
>>> door1 = Door(1, 'closed') >>> тип(дверь1) <класс '__main__.Дверь'> >>> напечатать(дверь1.номер) 1 >>> print(door1.status) закрыто
Теперь door1
является экземпляром класса Door
; type()
возвращает класс как __main__. Door
, поскольку класс был определен непосредственно в интерактивной оболочке, то есть в текущем основном модуле.
Чтобы вызвать метод объекта, то есть запустить одну из его внутренних функций, вы просто обращаетесь к нему как к атрибуту с точечным синтаксисом и вызываете его как стандартную функцию.
>>> door1.open() >>> напечатать(дверь1.номер) 1 >>> print(door1.status) открыть
В данном случае был вызван метод open()
экземпляра door1
. В метод open()
не были переданы никакие аргументы, но если вы просмотрите объявление класса, вы увидите, что он был объявлен для приема аргумента ( сам
). Когда вы вызываете метод экземпляра, Python автоматически передает сам экземпляр методу в качестве первого аргумента.
Вы можете создать столько экземпляров, сколько необходимо, и они совершенно не связаны друг с другом. То есть изменения, внесенные вами в один экземпляр, не отразятся на другом экземпляре того же класса.