Урок 5

Классы

Теперь переходим к азам ООП. Начать стоит с самых азов, а что же такое ООП? Что такое Класс? Зачем он нужен и как нам теперь с этим жить?

В принципе, мы уже вскользь касались многих вещей и понятий, теперь же стоит в них углубиться

Общине понятия

ООП (Объектно-ориентированное программирование) — методология программирования, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определенного класса, а классы образуют иерархию наследования. Простым языком — это манипуляция объектами.

Объект — некоторая сущность, обладающая определённым состоянием и поведением, имеющая заданные значения свойств (атрибутов) и операций над ними (методов). По сути, это экземпляр класса.

Класс — это элемент программы, описывающий абстрактный тип данных и его частичную или полную реализацию.

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

Наследование — описание нового класса, на основе существующего. Изначальный класс — родительский класс, новый класс — дочерний класс.

Полиморфизм — использование объектов с одинаковым интерфейсом без информации о типе и внутренней структуре объекта

При этом, классы могут иметь сложную структуру данных, но при использовании класса, нам это знать не нужно. Нам необходимо знать методы, для работы с ним, его интерфейс

Инкапсуляция — Объединение данных и методов, работающих с этими данными, в классе. По сути, внутренняя кухня класса

С основными понятиями покончено, для лучшего понимания приведу крайне простой и отвлеченный от Python пример:

Предположим, есть Родительский (супер) класс — Млекопитающих. Со своими свойствами (к примеру, 4 конечности, хвост и тд), методами (покушать, погладить и тд). И от него отнаследуем класс Собака. Получается, благодаря Наследованию, все свойства и методы, заложенные в родительский класс, добавятся собаке, при этом, мы можем добавить какие то дополнительные свойства и методы (к примеру, метод лаять). Это и есть наследование класса. Затем создадим элемент класса Собака — Бобик — это уже конкретный объект, со своими методами. Но благодаря тому, что мы знаем “интерфейс” класса, мы знаем, к примеру, как и чем его покормить, что он лает, как его гладить и что ему это нравится и тд.

Надеюсь, данный пример хоть немного смог прояснить основные понятия. Возвращаемся к Python.

Классы в Python

Объявление класса

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

class <class_name>:
    <class_code(params,method and etc)>

Для того что бы создать экземпляр класса, надо:

<var_name> = <class_name>()

Как я уже и сказал, у класса могут быть свои методы и свойства. Методы — по сути, простые функции. И вызывать метод надо так же, как мы делали раньше, вызывая методы разных классов (строк, изображений и тд) class_obj.method(). Но тут есть одно но. Когда объект класса вызывет метод, он передает методу сам себя, что бы можно было работь с параметрами конкретного объекта. По этому при прописывании методов, мы всегда должны указывть ключевое слово self. Теперь рассмотрим создание простого класса с одним методом — выводом текстовой строки на экран

Пример:

#Объявляем класс
class new_test_class:
    # Описываем метод вывода строки
    def say_hello(self):
        print("Hello from class!")

#Создаем объект созданного нами класса
new_class_obj = new_test_class()
# Дергаем метод
new_class_obj.say_hello()

Результат:

Hello from class!

Работа с классами

При создании объекта класса, происходит его первоначальная инициализация. И данную процедуру можно переопределить. Для этого надо просто описать функцию __init__()

Вообще, конструкция __ — обозначает приватность. Если дописать ее к переменной, то ее модифицировать можно будет только методом класса.

Пример:

class new_test_class:
    #Создадим два параметра (атрибуты)
    # __a - приватный, к нему нет доступа вне класса (если мы попытаемся к нему обратится, то получим ошибку)
    # Параметр b - публичный, мы можем что угодно делать с ним откуда угодно
    __a=3
    b=4
    #Функция инициализации, только выводит текст
    def __init__(self):
        print ("Class was created!")
    #Функция выводит текст и приватный параметр. Обращение идет через self
    #Потому что это в данном случае идет изменение параметра объекта, а в self
    #как раз сам объект и находится, так как в метод класса всегда передается объект, вызвавший этот метод
    #Затем приватной переменной присваивается значение 12
    def say_hello(self):
        print("Hello from class! "+ str(self.__a))
        self.__a = 12

print ("Now let's create obj")
a = new_test_class()
a.say_hello()
a.say_hello()
# Обращаемся к публичному атрибуту, если так же обратиться к приватному, будет ошибка
print (a.b)

Результат:

Now let’s create obj
Class was created!
Hello from class! 3
Hello from class! 12
4

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

Методы можно создавать на основе других методов, в этом случае они унаследуют все атрибуты и методы родителя. При этом никто не мешает дописать новые или переопределить уже имеющиеся. Для того, что бы выполнить наследование, при создании класса всего лишь необходимо указать имя родителя в скобках class <new_class>(<parent class>):

Пример:

#Объявляем родительский класс
#Создаем родительский класс, с парой атрибутов и методов
class new_test_class:
    a=3
    b=4
    #Функция инициализации, только выводит текст
    def __init__(self):
        print ("Class was created!")
    # Описываем метод вывода строки
    def say_hello(self):
        print("Hello from class! Privat param a = "+str(self.a))
    # Метод изменения приватного атрибута
    def change_param(self, arg):
        self.a=arg
# На основе родительского класса, создадим дочерний, у которого переопределим функцию инициализации
# И добавим новый метод
class child_class(new_test_class):
    def __init__(self):
        print ("This is new init for childe class")
    def sum(self):
        print ("{} + {} = {}".format(self.a,self.b,self.a+self.b))

#Создаем объект созданного нами класса
ob = new_test_class()
# Дергаем метод для вывода текста
ob.say_hello()
# Дергаем метод для изменеия приватного параметра
ob.change_param(57)
# Вновь дерним метод, что бы проверить приватный параметр, что он изменился
ob.say_hello()

# Визуально отделю вывод в консоле для нового класса
print()
# Создадим объект дочернего класса и дерним метод, который унаследовался у родительского
ob2 = child_class()
ob2.say_hello()
ob2.change_param(97)
ob2.say_hello()
# Дерним новый метод
ob2.sum()

Результат:

Class was created!
Hello from class! Privat param a = 3
Hello from class! Privat param a = 57

This is new init for childe class
Hello from class! Privat param a = 3
Hello from class! Privat param a = 97
97 + 4 = 101

!!!Важно!!!

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

В целом, этой информации должно быть достаточно для начала. Больше можно отыскать, как обычено, в документации

Исключения

Общие понятия

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

  1. SyntaxError — синтаксическая ошибка
  2. IndexError — Ошибка индекса, обращение к несуществующему индексу
  3. NameError — Ошибка использования несуществующей переменной
  4. TypeError — Ошибка при осуществлении действия с несовместимыми типами
  5. ValueError — Ошибка при попытки передать некорректное значение
  6. ImportError — Неверный импорт, попытка импорта несуществующей библиотеки

Пример:

print (z)

Результат:

Traceback (most recent call last):
File “”, line 1, in
NameError: name ‘z’ is not defined

Пример:

a=3
print ("Value a = "+a)

Результат:

Traceback (most recent call last):
File “”, line 1, in
TypeError: must be str, not int

В языке Python у разработчика есть возможность контролировать данные исключения. Это необходимо для того, что бы в случае возникновения ошибки, возникшего в связи с внешними условиями (ошибка ввода пользователя, или при взаимодействии с внешними системами), программа продолжала свое выполнение. Реализуется при помощи конструкции

try:
    <Операторы, которые могут выкинуть исплючение>
except [<ExeptionType>]:
    <Действия, которые необходимо сделать, в случае исключения>
except [<AnotherExeptionType>]:
    <Действия, которые необходимо сделать, в случае другого типа исключения>

Данная конструкция попытается выполнить какие то действия, и в случае возникновения исключения типа ExeptionType, программа выполнит одни действия, в случае другого типа исключения AnotherExeptionType — другие.
При этом можно не указывать тип исключения, в этом случае, будут отлавливаться все исключения, кроме синтаксических ошибок

Исключения будут ловиться только в блоке try!

Пример:

a=3
try:
    print ("Value a = " + a)
except TypeError:
    print ("Oops!")

Результат:

Oops!

Пример:

a=3
try:
    print ("Value a = " + a)
except:
    print ("Oops!")

Результат:

Oops!

Так же можно указывать разные типы не раздельно, а разом except (<ExeptionType1>,<ExeptionType2>...). Так же при помощи finaly: можно указать действия, которые будут выполнены в любом случае.

Пример:

a=3
try:
    print ("Value a = " + a)
except:
    print ("Oops!")
finally:
    print ("This will be executed in any case")

Результат:

Oops!
This will be executed in any case

Пример:

a=3
try:
    print ("Value a = " + str(a))
except:
    print ("Oops!")
finally:
    print ("This will be executed in any case")

Результат:

Value a = 3
This will be executed in any case

Собственные исключения

Иногда необходимо самостоятельно отслеживать какие то данные, и в случае несоответсвия ожиданиям, необходимо выбросить исключение. Для этого используется ключевое слово raise <ExeptionType>

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

try:
    a = 3
    if type(a)==int:
        raise TypeError
    print ("Type A !=int :)")
except:
    print ("Type a = int :(")

Результат:

Type a = int 🙁

Пример:

try:
    a = "3"
    if type(a)==int:
        raise TypeError
    print ("Type A !=int :)")
except:
    print ("Type a = int :(")

Результат:

Type A !=int 🙂

Пример:
Выбрасываем исключение, с остановкой программы, без попытки отловить, для демонстрации того, как выбрасываются исключения

a = 3
if type(a)==int:
    raise TypeError ("Some error text")
print ("Type A !=int :)")

Результат:

Traceback (most recent call last):
File “test5.py”, line 3, in
raise TypeError (“Some error text”)
TypeError: Some error text

Так же бывают случаи, когда исключение надо выкинуть и экстренно завершить программу, но перед этим программа что то должна сделать (к примеру, сохранить данные или что-то еще). Для этого так же можно применить raise, но уже в блоке except:
Пример:

try:
    print (10/0)
except ZeroDivisionError:
    print ("Oooops.... but sorry, i have to stop...")
    raise

Результат:

Oooops… but sorry, i have to stop…
Traceback (most recent call last):
File “test5.py”, line 2, in
print (10/0)
ZeroDivisionError: division by zero

Собственные типы исключений

Не редко, особенно при написании собственных библиотек, необходимо использовать новые типы исключений, которых нет в Python. Это делает через создания класса для собвственного исключения. Отличия от классов, что мы выше изучали, лишь в том, что он наследуется от родительского класса Exeption.

Пример:
В примере используется ключевое слово pass, которое буквально обозначате ничего не делать. Его исапользуют для создания пустых классов и функций

# Имя класса для исключений принято указывать с Error на конце. Это не обязательно, но так приянто
class  MyExeptionError(Exception):
    pass
raise MyExeptionError("Some non-required text for error")

Результат:

Traceback (most recent call last):
File “test6.py”, line 5, in
raise MyExeptionError(“Some non-required text for error”)
main.MyExeptionError: Some non-required text for error