Python объектно ориентированный подход

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

Временно воспользуемся простейшим описанием класса, с которого мы начали:

Создание нового экземпляра класса похоже на вызов функции:

В памяти компьютера по указанному после at адресу был создан новый объект типа __main__.Dog .

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

>>> a = Dog() >>> b = Dog() >>> a == b False 

Хотя a и b являются экземплярами класса Dog, они представляют собой два разных объекта.

Атрибуты класса и экземпляра

Теперь возьмем последнюю рассмотренную нами структуру класса:

class Dog: species = "Canis familiaris" def __init__(self, name, age): self.name = name self.age = age 

Для создания экземпляров объектов класса необходимо указать кличку и возраст собаки. Если мы этого не сделаем, то Python вызовет исключение TypeError :

>>> Dog() [. ] TypeError: __init__() missing 2 required positional arguments: 'name' and 'age' 

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

buddy = Dog("Buddy", 9) miles = Dog("Miles", 4) 

Но ведь в описании класса __init__() перечислены три параметра – почему в этом примере передаются только два аргумента?

При создании экземпляра Python сам передает новый экземпляр в виде параметра self в метод __init__() . Так что нам нужно беспокоиться только об аргументах name и age .

После того как экземпляры созданы, записанные данные доступны в виде атрибутов экземпляра:

>>> buddy.name 'Buddy' >>> buddy.age 9 >>> miles.name 'Miles' >>> miles.age 4 >>> buddy.species 'Canis familiaris' >>> miles.species 'Canis familiaris' 

Одним из важных преимуществ использования классов для организации данных является то, что экземпляры гарантированно имеют ожидаемые атрибуты. У всех экземпляров Dog гарантировано есть атрибуты species , name и age .

Значения атрибутов могут изменяться динамически:

>>> buddy.age = 10 >>> buddy.age 10 >>> miles.species = "Felis silvestris" >>> miles.species 'Felis silvestris' 

Экземпляры не зависят друг от друга. Изменение атрибута класса у одного экземпляра не меняет его у остальных экземпляров:

>>> buddy.species 'Canis familiaris' 

Методы экземпляра

Методы экземпляра – это определенные внутри класса функции, которые могут вызываться из экземпляра этого класса. Так же, как и у метода __init__() , первым параметром метода экземпляра всегда является self :

class Dog: species = "Canis familiaris" def __init__(self, name, age): self.name = name self.age = age # Метод экземпляра def description(self): return f" is years old" # Другой метод экземпляра def speak(self, sound): return f" says " 

Мы добавили два метода экземпляра, возвращающих строковые значения. Метод description возвращает строку с описанием собаки, метод speak принимает аргумент sound :

>>> miles = Dog("Miles", 4) >>> miles.description() 'Miles is 4 years old' >>> miles.speak("Woof Woof") 'Miles says Woof Woof' >>> miles.speak("Bow Wow") 'Miles says Bow Wow' 

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

К примеру, когда вы создаете объект списка, вы можете использовать для отображения функцию print() :

>>> names = ["Fletcher", "David", "Dan"] >>> print(names) ['Fletcher', 'David', 'Dan'] 

Посмотрим, что произойдет, когда мы попробуем применить print() к объекту miles :

В большинстве практических приложений информация о расположении объекта в памяти не очень полезна. Поведение объекта при взаимодействии с функцией print() можно изменить, определив специальный метод __str__() :

class Dog: species = "Canis familiaris" def __init__(self, name, age): self.name = name self.age = age def __str__(self): return f" is years old" def speak(self, sound): return f" says " 
>>> miles = Dog("Miles", 4) >>> print(miles) Miles is 4 years old 

Двойные символы подчеркивания в таких методах, как __init__() и __str__() указывают на то, что они имеют предопределенное поведение. Есть множество более сложных методов, которые вы можете использовать для настройки классов в Python, но это тема отдельной публикации.

Наследование от других классов в Python

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

Пример: место для выгула собак

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

Мы можем изменить класс Dog , добавив атрибут breed (англ. порода):

class Dog: species = "Canis familiaris" def __init__(self, name, age, breed): self.name = name self.age = age self.breed = breed def __str__(self): return f" is years old" def speak(self, sound): return f" says " 

Смоделируем несколько псов разных пород:

miles = Dog("Miles", 4, "Jack Russell Terrier") buddy = Dog("Buddy", 9, "Dachshund") jack = Dog("Jack", 3, "Bulldog") jim = Dog("Jim", 5, "Bulldog") 

У каждой породы собак поведение несколько отличаются. Например, разные породы по-разному лают: одни говорят «гав», другие делают «вуф». Используя только класс Dog , мы были бы должны указывать строку для аргумента sound метода speak() каждый раз, когда вызываем его в экземпляре Dog :

>>> buddy.speak("Yap") 'Buddy says Yap' >>> jim.speak("Woof") 'Jim says Woof' >>> jack.speak("Woof") 'Jack says Woof' 

Передавать строку в каждый вызов метод speak() неудобно. Более того, строка, соответствующая звуку, который издает экземпляр, в идеале должна определяться атрибутом breed .

Один из вариантов упростить взаимодействие с классом Dog – создать дочерний класс для каждой породы. Это позволит расширить функциональные возможности наследующих дочерних классов. В том числе можно будет указать аргумент по умолчанию для speak .

Создаём дочерние классы

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

class Dog: species = "Canis familiaris" def __init__(self, name, age): self.name = name self.age = age def __str__(self): return f" is years old" def speak(self, sound): return f" says " 

Связь между родительским и дочерним классом определяется тем, что наследуемый класс ( Dog ) передается в качестве аргумента, принимаемого дочерним классом:

class JackRussellTerrier(Dog): pass class Dachshund(Dog): pass class Bulldog(Dog): pass 

Дочерние классы действуют так же, как родительский класс:

miles = JackRussellTerrier("Miles", 4) buddy = Dachshund("Buddy", 9) jack = Bulldog("Jack", 3) jim = Bulldog("Jim", 5) 

Экземпляры дочерних классов наследуют все атрибуты и методы родительского класса:

>>> miles.species 'Canis familiaris' >>> buddy.name 'Buddy' >>> print(jack) Jack is 3 years old >>> jim.speak("Woof") 'Jim says Woof' 

Чтобы определить, к какому классу принадлежит определенный объект, используйте встроенную функцию type() :

>>> type(miles) __main__.JackRussellTerrier 

Чтобы определить, является ли miles экземпляром класса Dog , используем встроенную функцию isinstance() :

Объекты miles , buddy , jack и jim являются экземплярами Dog, но miles не является экземпляром Bulldog , а jack не является экземпляром Dachshund :

>>> isinstance(miles, Bulldog) False >>> isinstance(jack, Dachshund) False 

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

Теперь дадим нашим собакам немного полаять.

Расширяем функциональность родительского класса

Что мы хотим сделать: переопределить в дочерних классах пород метод speak() . Чтобы переопределить метод, определенный в родительском классе, достаточно создать метод с тем же названием в дочернем классе:

class JackRussellTerrier(Dog): def speak(self, sound="Arf"): return f" says " 

Мы переопределили метод speak , добавив для породы JackRussellTerrier значение по умолчанию.

>>> miles = JackRussellTerrier("Miles", 4) >>> miles.speak() 'Miles says Arf' 

Мы по-прежнему можем передать какой-то иной звук:

>>> miles.speak("Grrr") 'Miles says Grrr' 

Изменения в родительском классе автоматически распространяются на дочерние классы. Если только изменяемый атрибут или метод не был переопределен в дочернем классе.

class Dog: species = "Canis familiaris" def __init__(self, name, age): self.name = name self.age = age def __str__(self): return f" is years old" def speak(self, sound): return f" barks " 
>>> miles = JackRussellTerrier("Miles", 4) >>> miles.speak() 'Miles says Arf' 

Иногда бывает необходимо учесть и поведение родительского класса, и дочернего, например, вызвать аналогичный метод родительского класса, но модифицировать его поведение. Для вызова методов родительского класса есть специальная функция super :

class JackRussellTerrier(Dog): def speak(self, sound="Arf"): return super().speak(sound) 
>>> miles = JackRussellTerrier("Miles", 4) >>> miles.speak() 'Miles barks Arf' 

Здесь при вызове super().speak(sound) внутри класса JackRussellTerrier , Python ищет родительский класс Dog (на это указывает функция super() ), и вызывает его метод speak() с переданной переменной sound . Именно поэтому выводится глагол barks , а не says , но с нужным нам звуком Arf , который определен в дочернем классе.

В приведенных примерах иерархия классов очень проста. Класс JackRussellTerrier имеет единственный родительский класс Dog . В реальных примерах иерархия классов может быть довольно сложной.Функция super() делает гораздо больше, чем просто ищет в родительском классе метод или атрибут. В поисках искомого метода или атрибута функция проходит по всей иерархии классов. Поэтому без должной осторожности использование super() может привести к неожиданным результатам.

Заключение

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

  • в чём отличия классов и экземпляров;
  • как определить класс;
  • как инициализировать экземпляр класса;
  • как определить методы класса и методы экземпляра;
  • как одни классы наследуются от других.

Как научиться программировать на Python максимально быстро и качественно?

В условиях повышенной конкуренции среди джунов, пойти учиться на курсы с преподавателями — самый прагматичный вариант, который позволит быстро и качественно освоить базовые навыки программирования и положить 5 проектов в портфолио. Преподаватель прокомментирует домашние задания, поделится полезными советами, когда надо подбодрит или даст «волшебного» пинка.

На курсе «Основы программирования на Python» с преподавателем вы научитесь:

  • работать в двух интегрированных средах разработки — PyCharm и Jupyter Notebook;
  • парсить веб-страницы;
  • создавать ботов для Telegram и Instagram;
  • работать с данными для различных материалов и дальнейшего анализа;
  • тестировать код.

Плюс положите 5 проектов в портфолио.

Источники

Источник

Читайте также:  Return variable in java
Оцените статью