Объектно-ориентированное программирование на Python
Python – это новый язык программирования. Он был разработан в 1991 году программистом Гвидо ванн Россумом. В отличие от многих других языков, в Python все сущности являются объектами. Также язык имеет динамическую типизацию и автоматическое управление памятью. Авторы языка старались сделать его максимально удобным для разработчиков, поэтому синтаксис языка довольно минимальный. К недостаткам Python можно отнести низкую скорость работы и большое потребление памяти в отличие от компилируемых языков.
Сегодня Python занимает 3 место в рейтинге TIOBE среди самых популярных языков мира. Его используют в разных сферах: от машинного обучения и Big data до разработки игр и приложений. Благодаря простому синтаксису язык используют в процессе обучения программированию. Python позволяет сосредоточиться на изучении алгоритмов и принципов программирования, а не на изучении синтаксиса.
Принципы объектно-ориентированного программирования
Программист, использующий Python в своих проектах может пользоваться разными методологиями и парадигмами программирования. Но в основе языка лежит именно объектный подход. В виде объектов здесь представлено всё, даже простые числа – это объекты.
Принципы ООП, реализованные в Python:
- Всё, что есть в языке – это объекты, даже простые числа представлены в виде объектов. Объект имеет определённый тип, по-другому – класс, реализующий логику объекта.
- Помимо объектов, в ООП есть ещё и классы. Они обозначаются зарезервированным словом – class и представляют собой абстрактный набор методов и полей.
- Внутри каждого объекта находиться слоя логика, которая позволяет как-то общаться с этим объектом. Такой принцип называется инкапсуляция. В рамках инкапсуляции программист может ограничить доступ к логике объекта.
- Все объекты, созданные программистом, должны общаться между собой.
- Объект может включать в себе другой объект, представленный в виде поля.
- Одна из базовых концепций ООП – наследование. Оно позволяет устанавливать отношение родитель-потомок между объектами.
- Полиморфизм – вторая концепция ООП – позволяет объекту самому определять реализацию методов.
- Инкапсуляция – ограничивает доступ к данным.
Основы ООП
Чтобы успешно писать код на Python придётся разобраться с концепцией объектно-ориентированного программирования.
Классы
Классы представляют абстрактную структуру, которая содержит в себе логику для объекта. Пример класса:
classMyClass(object):
Для создания класса указывается зарезервированное слово class, после которого указывается имя. В скобках можно указать родительские классы. В данном примере мы указываем класс object в качестве родителя. Python поддерживает множественное наследование.
Пример класса с наследованием:
classMyClass(parent1, parent2):
Каждый класс имеет свойства и методы. Они задаются после «:». Пример класса со свойствами:
classMyClass(object):
x = 5
str = ‘Hello’
Непривычной особенностью языка для многих программистов является то, что свойству не нужно указывать тип. Компилятор языка сам разберётся с типизацией.
Помимо свойств, класс может иметь и методы. Они задаются как обычная функция, но с одним отличием. В качестве первого параметра методу обязательно нужно передать аргумент self. Пример класса с методом:
classMyClass(object):
defMyMethod(self, x):
После «:» указывается код метода.
Объекты класса
Объект класса (экземпляр) – это конкретная реализация класса. В Python работать с экземплярами довольно просто. Пример:
classSomeClass(object):
defSomeMethod(self, x):
returnx*x
Для того, чтобы создать объект этого класса, нужно:
someObj = SomeClass()
y = someObj.SomeMethod(6)
print(y)
В результате работы программы на экран выведется число 36.
Чтобы создать объект, нужно создать переменную и присвоить ей имя класса. Далее проиллюстрирован механизм доступа к членам объекта при присваивании значения переменной y. С помощью точки можно не только вызывать методы и функции, но и обращаться к полям объекта.
В Python есть возможность создать объект с некоторым набором параметров. Пример:
class Point(object):
Def __init__(self, x, y, z):
self.coord(x, y, z)
p1 = Point(13, 14, 15)
В этом примере мы создали класс Point, который будет хранить в себе 3 координаты. Затем, создали экземпляр этого класс p1. Этот экземпляр представляет конкретную точку с координатами 13, 14 и 15. Обратиться к этим координатам можно так: p1.coord
Динамический изменяемые классы
В отличии от многих других языков, где класс – это неизменяемая структура, в Python программист может добавлять методы в класс прямо во время его работы. Для этого используется зарезервированное слово pass. Вот так создаётся динамический класс:
classSomeClass(object) :pass
Подобный класс абсолютно бесполезен, пока мы не добавим в него какую-нибудь логику. Пример работы с динамическим классом:
classSomeClass(object) :
pass
def method(self, x) :
return x*x
SomeClass.square = method
someObj = SomeClass
someObj.square(5)
Результат работы программы: 25. В этом примере мы дополняем класс новым методом, который будет доступен из любых объектов.
Статические и классовые методы
Статические методы используются во многих языках программирования. Внешне они похожи на другие методы, но с одним отличием. Вызывать статические методы можно как из класса, так и из его экземпляра. Для того чтобы их создать, нужно воспользоваться специальным декоратором: @staticmethod. Пример:
classSomeClass(object) :
@staticmethod
def Hello() :
print(‘Hello’)
SomeClass.Hello() #В результате вызовется метод Hello класса SomeClass
someObj = SomeClass()
someObj.Hello() #В этой строке мы обращаемся к методу Hello, но не класса, а экземпляра
В результате работы программы на экране дважды выведется надпись Hello.
Ещё существуют классовые методы, которые выполняются в контексте класса. Чтобы создать классовый метод, нужно указать декоратор @classmethod. Важной особенностью таких методов является обязательный параметр-ссылка на класс – cls. Пример:
classSomeClass(object) :
@classmethod
def Hello(cls) :
print(‘Hello, класс{}’.format(cls.__name__))
SomeClass.Hello()
В результате на кран выведется «Hello, класс SomeClass». Программистам-новичкам не всегда бывает понятно применение классовых методов. Самый яркий пример использования – фабрика объектов.
Объекты и их методы
В Python существует довольно много специальных методов, например__init__. Эти методы введены в язык для того, чтобы управлять жизнью объекта.
Жизнь объекта
Жизненный цикл объекта включат 3 этапа — создание, работа и удаление их из памяти.Всеми этапами программист может управлять. Например, от разработчика зависит, когда объект будет проинициализирован. Для этого можно использовать метод __init__ либо __new__
Пример использования инициализатора __init__ и __new__:
classSomeClass(object):
def __new__(cls):
print(‘new’)
return super(SomeClass, cls).__new__(cls)
def __init__(self):
print(‘init’)
obj = SomeClass();
В результате работы программы выведется new init.
На первый взгляд,__new__ может показаться бесполезным, но это не так. Метод полезен, например, при реализации паттерна «Одиночка». Пример реализации:
classSingleton(object):
obj = None # единственный экземпляр класса
def __new__(cls, *args, **kwargs):
if cls.obj is None:
cls.obj = object.__new__(cls, *args, **kwargs)
return cls.obj
single = Singleton()
single.attr = 42
newSingle = Singleton()
newSingle.attr # 42
newSingleissingle # true
Программист может не только создавать объекты, но и удалять их. Для этого существует метод-деструктор.
Пример с удалением объекта:
classSomeClass(object):
def __init__(self, name):
self.name = name
def __del__(self):
print(‘удаляется объект {} классаSomeClass’.format(self.name))
obj = SomeClass(«John»);
delobj
Результат работы программы: удаляется объект John класса SomeClass. Однако, часто использовать деструкторы – не очень хорошо. Python сам определяет ненужные объекты и удаляет их.
Объект в виде функции
Существует специальный метод __call__, который позволяет вызывать объект точно так же, как и функцию. Пример:
class Multiplier:
def __call__(self, x, y):
return x*y
multiply = Multiplier()
multiply(19, 19) # 361
multiply.__call__(19, 19) # то же самое, что и в предыдущей строке
Имитация контейнера
Многие знакомы с функцией len(). Она позволяет узнать длину списка из каких-то объектов, но это работает только для просты типов. Пример:
class Collection:
def __init__(self, list):
self.list = list
collection = Collection(list)
len(collection)#в этой строке возникнет ошибка «Object of type has no len()»
Это означает, что интерпретатор Python просто не понимает, как ему считать длину. Для решения этой проблемы, был придуман метод __len__. Пример без ошибки:
class Collection:
def __init__(self, list):
self.list = list
def __len__(self):
returnlen(self.list)
collection = Collection([1, 2, 3])
len(collection) # 3
Другие специальные методы
Помимо описанных выше, в Python определено большое количество разнообразных методов. Разработчик может изменить вид объекта при печати, определить, как конкретный объект будет преобразовываться в строку, изменить способ сравнения нескольких экземпляров и много другое. Этих методов очень много, подробно они описаны в документации языка.
Проблема с доступом к атрибутам
Объект, у которого есть несколько родительских классов, будет также иметь несколько __getattribute__ и других специальных методов. Как в таком случае компилятор обрабатывает запросы к специальным методам?
Для того чтобы разобраться с этим вопросом, рассмотрим запрос obj.field.
- В начале, компилятор вызывает специальный метод, пусть в нашем случае это будет __getattribute__(field). В качестве параметра он принимает field
- Затем, компилятор читает все пользовательские атрибуты, которые записаны в __dict__ .
- Если второй шаг не дал результата, то компилятор пытается найти атрибут в obj.__class__.__slots__
- С помощью рекурсии атрибут ищется у родительских классов в поле __dict__. Если объект имеет не одного родителя, то порядок поиска соответствующего атрибута такой же, как и порядок определения родительских классов.
- Вызывается метод __getattr__, если он определён.
- Если предыдущие шаги не дали никакого результата, то компилятор выбрасывает исключение: AttributeError, которое говорит он том, что нужного атрибута нет.
Если атрибут всё-таки был найден, то вызывается метод __get__, __set__ или __del__.
Принципы ООП в Python
Существует 3 основных концепции работы с ООП: инкапсуляция, наследование и полиморфизм. В разных языках они реализуются по-разному.
Инкапсуляция
Инкапсуляция – это механизм предоставления доступа к данным объекта. Например, любой атрибут мы можем объявить приватным (то есть не доступным для кода вне класса) с помощью нижнего подчёркивания.
classSomeClass(object) :
def _Hello(sefl) : #Перед именем метода стоить «_», то есть метод приватный
print(‘Hello’)
obj = SomeClass()
obj._Hello()#Метод на самом деле не приватный
В данном случае мы можем вызвать метод Hello у объекта, так как на самом деле он не является приватным. Ограничение доступа происходит лишь на уровне соглашения между разработчиками. Если другой программист Python увидит метод, название которого начинается с нижнего подчёркивания, то он будет знать, что этот метод приватный и вызывать его не стоит.
Существует возможность полностью ограничить доступ к методу. Делается это с помощью двух нижних подчёркиваний. Пример:
classSomeClass(object) :
def __Hello(sefl) :
print(‘Hello’)
obj = SomeClass()
obj.__Hello() #метод недоступен
Механизм инкапсуляции также включает другие специальные методы: геттеры, сеттеры и деструкторы. Они нужны для доступа к атрибутам объекта.
Пример работы со специальными методами:
classSomeClass():
def __init__(self, value):
self._value = value
defgetvalue(self): # получение значения атрибута
returnself._value
defsetvalue(self, value): # установка значения атрибута
self._value = value
defdelvalue(self): # удаление атрибута
delself._value
value = property(getvalue, setvalue, delvalue, «Свойство value»)
В данном примере показаны три специальных метода: setvalue–используется для присваивания значения свойству value, getvalue–используется для чтения и delvalue–деструктор, которые удаляет свойство.
Наследование
Механизм наследования позволят устанавливать между классами отношение родитель-потомок. В дочернем классе будут доступны все поля и методы родительского класса.
Пример одиночного наследования:
classMammal():
className = ‘Mammal’
class Dog(Mammal):
species = ‘Canis lupus’
dog = Dog()
dog.className#Поле className доступно
В данном примере родительским классом является класс – Mammal, а дочерним – Dog. При этом из объекта dogмы можем вызвать поля и методы родительского класса.
Помимо одиночного, Python поддерживает и множественное наследование. Пример:
class Horse():
isHorse = True
class Donkey():
isDonkey = True
class Mule(Horse, Donkey):
mule = Mule()
mule.isHorse #Поля доступны
mule.isDonkey
Механизм наследования необходим для разделения логики между классами и повторного её использования. Например, есть класс Person, содержащий поле Age и метод Sleep. Также есть класс Pety, в котором доступны поля из Person, но Pety содержит ещё метод Work. Суть такого разделения заключена в том, что каждый человек может спать, но не каждый может работать. Петя может и спать, и работать.
Ассоциация
Если класс имеет поля, которые тоже являются классами, то такой механизм будет называться ассоциацией. Она подразделяется на композицию и агрегацию. Нередко начинающие программисты неправильно используют ассоциацию, из-за чего может возникать утечка памяти.
Пример композиции:
classSalary:
def __init__(self,pay):
self.pay = pay
defgetTotal(self):
return (self.pay*12)
class Employee:
def __init__(self,pay,bonus):
self.pay = pay
self.bonus = bonus
self.salary = Salary(self.pay)
defannualSalary(self):
return «Total: » + str(self.salary.getTotal() + self.bonus)
employee = Employee(100,10)
print(employee.annualSalary())
Пример агрегации:
class Salary(object):
def __init__(self, pay):
self.pay = pay
defgetTotal(self):
return (self.pay * 12)
class Employee(object):
def __init__(self, pay, bonus):
self.pay = pay
self.bonus = bonus
defannualSalary(self):
return «Total: » + str(self.pay.getTotal() + self.bonus)
salary = Salary(100)
employee = Employee(salary, 10)
print(employee.annualSalary())
Полиморфизм
Последняя концепция ООП – полиморфизм. Он позволяет объекту самому определять своё поведение. В Python это сделано через виртуальные методы. Дочерний класс может переопределить поведение метода родительского класса. Такой механизм позволяет решить задачу разным путём.
classMammal:
defmove(self):
print(‘Двигается’)
class Hare(Mammal):
def move(self):
print(‘Прыгает’)
animal = Mammal()
animal.move() # Здесь вызывается метод move, класса Mammal
hare = Hare()
hare.move() # А здесь уже вызывается метод класса Hare
Как видно из примера, дочерний класс Hareпереопределил метод move, то есть добавил в него свою логику.
Чтобы получить доступ к методу базового класса, можно использовать зарезервированное слово super.
Пример с доступом к методу базового класса:
class Parent():
def __init__(self):
print(‘Parent init’)
def method1(self):
print(‘Parent method’)
class Child(Parent):
def __init__(self):
Parent.__init__(self)
def method1(self):
super(Child, self).method()
child = Child()
child.method1()
Несмотря на то, что в дочернем классе переопределён method1, вызовется всё равно метод базового класса.
Подобные способы – не единственные варианты реализации полиморфизма. В Python есть так называемая утиная типизация. Она позволяет классам иметь одинаковый интерфейс, но с разной реализацией. Под интерфейсом здесь понимается одинаковое название методов и полей.
Пример:
class English:
def greeting(self):
print («Hello»)
class French:
def greeting(self):
print («Bonjour»)def intro(language):
language.greeting()
john = English()
gerard = French()
intro(john) # Hello
intro(gerard) # Bonjour
В данном примере реализованы два класса English и French. Оба они содержат метод с одинаковым названием. Также в самой программе прописан метод intro, который в качестве параметра принимает объект language. Не зависимо от типа объекта, который мы передали вintro, у переданного параметра будет вызван метод greeting. Главное, чтобы объект language содержал в себе реализацию метода с таким названием.
Множественная диспетчеризация
Благодаря виртуальным методам программист реализует одиночную диспетчеризацию, но в Python есть возможность использовать и множественную. Этот механизм позволяет выбирать функциональность исходя из количества параметров, типов и аргументов.
Множественная диспетчеризация доступна в Python благодаря мультиметодам. К сожалению, разработчики не добавили поддержку мультиметодов в язык, но существует большое количество библиотек, которые позволяют их использовать. Одна из таких библиотек – multimethods.py.
Метаклассы
Помимо обычных классов, в Python доступны и метаклассы. От обычных они отличаются тем, что в качестве метода-инициализатора (__init__) они используют другой класс.
Пример работы с метаклассами:
classMetaClass(type):
# выделение памяти для класса
def __new__(cls, name, bases, dict):
print(«Создание нового класса {}».format(name))
returntype.__new__(cls, name, bases, dict)
# инициализация класса
def __init__(cls, name, bases, dict):
print(«Инициализация нового класса {}».format(name))
return super(MetaClass, cls).__init__(name, bases, dict)
# порождение класса на основе метакласса
SomeClass = MetaClass(«SomeClass», (), {})
# обычное наследование
class Child(SomeClass):
def __init__(self, param):
print(param)
# получение экземплярак ласса
obj = Child(«Hello»)
Python – современный интерпретируемый язык. Несмотря на то, что язык поддерживает большое количество парадигм, основная концепция – ООП. В эту парадигму входят три основных понятия: инкапсуляция – сокрытие данных, наследование – установление связи родитель-потомок и полиморфизм – многообразие реализации.
ООП на Python имеет ряд особенностей, которые отличают его от других языков. Например, классы – это тоже объект. Инкапсуляция основана лишь на соглашении между разработчиками.