Классы, наследование, init, self, *args и **kwargs в Python

Классы, наследование, init, self, *args и **kwargs в Python

У многих Junior Python-программистов возникают проблемы с классами. Сегодня подробно разберём применение классов в Python, а также затронем тему *args и **kwargs. 

Почему эти темы трудные? На начальном уровне программирования нам не требуется строить громоздкие конструкции. Обычно мы ограничиваемся скриптами без собственных классов. Или же создаем объекты классов, не задумываясь о том, что внутри. 

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

Знакомство с классами в Python

Что потребуется:

  1. Компьютер или любая другая ЭВМ
  2. Редактор кода
  3. Python версии 3.9 и выше
  4. Соединение с интернетом (впрочем можно и без него)

Начнем разбираться: для чего нужен класс? Он нужен для создания объектов с определенными функциями и данными внутри. То есть класс упрощает нам жизнь. Если у нас есть функционал взаимодействия с чем-либо и для этого нужно вызывать много функций, будет проще объединить все функции в один объект.

Например, мы взаимодействуем с базой данных. Нам приходится выполнять ряд однотипных действий:

  1. Подключиться к базе
  2. Найти нужную нам таблицу
  3. Сделать поиск значений по каким-то параметрам
  4. Вывести полученные значения.

Таких действий может быть и больше. Скоро нам надоест руками перебирать все функции для работы с БД. Тут и вступают в игру классы. 

Рассмотрим на примере кода:

class Person:
    def __init__(self, sex: str):
        self.sex = sex
        self.gender = 'heterosexual'
        self.age = 0
        print('I was born')

    def change_gender(self, preferred_gender: str):
        self.gender = preferred_gender

    def describe_the_action(self, action: str):
        print('I do ', action)

    def __del__(self):
        print('I am dead')

Сразу уточним, что функции в рамках класса будут называться методами, а переменные - полями. Обратите внимание - в Python принято записывать имя класса с большой буквы, а имена методов - с маленькой.

Метод __init__ - это конструктор, он автоматически вызывается при создании объекта. Как только мы создали новый объект, нам выведется ‘I was born’. 

Все методы принимают обязательный элемент self - он указывает, что мы обращаемся именно к этому объекту, а не к какому-то другому. 
Для пояснения self есть красочный пример со stackoverflow:

У нашей персоны будет его пол, гендер (стандартный при рождении) и возраст (изначально он будет равен 0). Дальше передаем аргументы.

Теперь немного о полях: они обозначаются обращением к self через точку и название поля.

Метод __del__ - это деконструктор, который срабатывает когда объект уничтожается (вызов del object или завершение скрипта).

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

Создаем объект на основе класса и передаем его пол "male":

person = Person('male')

Давайте посмотрим что выведется и попробуем изменить пол:

print('gender:', person.gender)
person.change_gender('cat')
print('gender:', person.gender)

Выводы в консоль:

I was born

gender: heterosexual

gender: cat

I am dead

Тут мы видим, что все работает корректно. Предлагаю вам самостоятельно поиграться с классом персон.

Наследование класса

Итак, мы хотим создать новый класс на базе Person. Для этого после названия класса в скобках пишем класс, от которого хотим наследоваться: 

class Ivan(Person):
    def __init__(self, sex: str, name: str):
        super().__init__(sex)
        self.name = name

Класс Ivan унаследует все методы и поля, которые есть у родителя Person. 

Также, надо сделать инициализацию класса родителя. С этим нам поможет  super().__init__(sex). После чего добавляем имя для Ivan, чего не было у Person. Теперь можем создать объект "man" и провести с ним некоторые операции:

man = Ivan('male', 'Ivan')
print('gender:', man.gender)
man.change_gender('cat')
print('gender:', man.gender)

Вывод в консоль:

I was born

gender: heterosexual

gender: cat

I am dead

Не забывайте, что обращаться к полям класса можно через точку:

print(man.name)

Вывод в консоль:

Ivan

Теперь вы убедились, что  Ivan унаследовал все методы от Person + к ним добавилось поле "name".

Секреты *args и **kwargs

Что это за звёздочки и зачем они нужны? Начнём с *args.

Неважно какое будет название у аргумента, важен сам оператор - звёздочка. В нашем примере звёздочка сообщает Python, что если пользователь укажет больше двух аргументов, все лишние аргументы передадутся в кортеж под названием other: 

def arguments(first, second, *other):
    print('first:', first)
    print('second:', second)
    print('other:', other)
    print(type(other))

arguments(1, 2, 3, 4, 5, 6, 7, 8)

Давайте посмотрим что выведется в консоль:

first: 1

second: 2

other: (3, 4, 5, 6, 7, 8)

<class 'tuple'>

Первые аргументы, поступающие в функцию, имеют свои названия - first и second. Все остальные аргументы попали в кортеж (tuple). К other нужно обращаться по индексу, как и к обычному кортежу (подробнее о tuple тут).

Что будет, если мы не передадим в other никаких аргументов?

arguments(1, 2)

Вывод в консоль:

first: 1

second: 2

other: ()

<class 'tuple'>

Мы просто получим пустой кортеж - никаких ошибок не будет.

Теперь разберёмся с **kwargs.

def keyword_arguments(first, second, **other):
    print('first:', first)
    print('second:', second)
    print('other:', other)
    print(type(other))
    
keyword_arguments(1, 2, nums=[1, 2, 3, 3], string='text', dict={'key': 'value'})

У ваc может возникнуть вопрос - что за новые аргументы nums, string и dict? 
Давайте запустим и посмотрим:

first: 1

second: 2

other: {'nums': [1, 2, 3, 3], 'string': 'text', 'dict': {'key': 'value'}}

<class 'dict'> 

Удивительно, но other стал словарем с ключами в виде названий аргументов. Как мы видим, туда можно передавать любое количество аргументов с названием и значением - они будут доступны в виде словаря.

Если мы задаём всего 2 аргумента - словарь просто останется пустым:

keyword_arguments(1, 2)

Вывод в консоль:

first: 1

second: 2

other: {}

<class 'dict'> 

Как видите, ошибок нет, мы получили пустой словарь.

Заключение

Темы классов и аргументов не просты в освоении. Надеюсь, мне удалось пролить больше света на эти удивительные возможности Python. 

Чем крупнее будут становиться проекты, тем больше вам придётся иметь дело с классами. Без них просто невозможно управлять сложными объектами с большим количеством функций. 

Ну а если вы не знаете заранее какие аргументы и в каком количестве вам нужны для функции, на помощь всегда придут *args и **kwargs. 

Чтобы понять и закрепить материал, я рекомендую самостоятельно поиграться с классами, с *args и **kwargs. Лучше, если вы примените их на реальном проекте, чтобы как следует разобраться и усвоить детали.

Комментарии