- Создание экземпляра класса в Python
- Атрибуты класса и экземпляра
- Методы экземпляра
- Наследование от других классов в Python
- Пример: место для выгула собак
- Создаём дочерние классы
- Расширяем функциональность родительского класса
- Заключение
- Как научиться программировать на 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 проектов в портфолио.