Автор: Шабанов Павел
E-mail: meteomail@yandex.ru
В этой 4 части "Конструктора" поговорим о Объектно-Ориентированном стиле программирования в python.
А не замахнуться нам в Python на Объектно, так сказать, Ориентированный стиль программирования? И замахнёмся!
Итак, объектно-ориентированное программирование (ООП), это такой стиль программирования, в котором мы используем не только арифметико-логические операторы и встроенные типы данных, но и производные типы данных или классы.
Класс (Class) - это абстрактный тип данных, который нужен для более точного описания какого-либо конкретного реально существующего в той или иной реальности объекта. Говоря другими словами класс стремится описывать какой-то предмет (может быть и очень абстрактный предмет) в терминах его свойств (функции или методы класса) и характеристик (переменные или атрибуты класса).
Пример
Как описать на языке программирования автомобиль? Можно написать формулы его перемещения, провести численный эксперимент или краш-тест, но описать авто в терминах целых чисел, строк и даже списков сложновато. Нужно будет иметь недюжую фантазию, чтобы создать в голове единый образ автомобился на основе информации, представленной слабо связанными логически разными типами данных.
Но можно создать класс "Автомобиль", описав его численные характеристики (число дверей, мошность двигателя, клиренс, год выпуска), символьные характеристики (марка автомобиля, имя владельца, описание интерьера салона) в виде соответсвующих атрибутов (переменных), а свойства (перемещение, смена резины) в виде методов (функций). Тогда будет создана единая структурная единица, информационный макет автомобиля с которым намного легче работать.
Класс - это платоновская идея, а экземпляр класса - это конкретное воплощение платновской идеи (вещь) в подлунном мире. То есть класс "Автомобили" описывает предмет автомобиля с самых общих сторон, а конкретное описание конкретной машине производится при создании экземпляра идеи автомобиля (создание экземпляра класса). Или экземпляр класса - это конкретный автомобиль конкретного владельца, тогда как класс является своеобразным ГОСТ-ом "Механизированное транстпортное средство". Таковы самые общие идеи ООП.
Как же перейти от чистого структурного программирования к стилю ООП? Просто. Функции надо объединять в класс, а при работе в основной программной единице создавать необходимые экземпляры классов.
import numpy as np
class MyBigData:
def __init__(self, x, y, name='Sample'):
'''
Это функция называется конструктором класса, так как выполняется \
при любом создании экземпляра класса
'''
self.x = x
self.y = y
self.name = name
def __str__(self):
s = 'Hello, World!'
s += ' This is %s data' % self.name
return s
def extrem(self):
H = (np.min(self.y), np.max(self.y))
print('Ymin %.1f Ymax %.1f' % (np.min(self.y), np.max(self.y)))
return H
x = np.arange(1,11)
y = np.random.random(np.shape(x))
z = MyBigData(x, y) # Создаём экземпляр класса
H = z.extrem() # Узнаем об экстремумах вектора y
Ymin 0.1 Ymax 0.8
Мы создали класс MyBigData в которой определили две функции. Функции для класса называют методами, а переменные - атрибутами. У созданного класса есть атрибуты, которые появляются как только мы создадим экземпляр класса z: это x, y и name (name является параметром по умолчанию). Такую инициализацию или первоначальное наполнение класса атрибутами позволяет сделать особая функция или особый метод - "init". Функция/метод с таким названием будет сразу выполнена при создании экземпляра класса.
Поговорим о нотации. Работая с конструктором класса (функцией init), и вообще с любым методом класса (функцией), мы передаём первым обязательным параметром слово self. Это не магическое слово, не заклинание, просто так принято. Проверим, что будет, если подставить вместо self другое слово (цифры, очевидно, не подойдут).
# Тот же код, но с разными словами вместо self
import numpy as np
class MyBigData:
def __init__(robot, x, y, name='Sample'):
'''
Это функция называется конструктором класса, так как выполняется \
при любом создании экземпляра класса
'''
robot.x = x
robot.y = y
robot.name = name
def __str__(fish):
s = 'Hello, World!'
s += ' This is %s data' % fish.name
return s
def extrem(fool):
H = (np.min(fool.y), np.max(fool.y))
print('Ymin %.1f Ymax %.1f' % (np.min(fool.y), np.max(fool.y)))
return H
Как видно из примера, принципиально лишь наличие кодового слова при объявлении-создании метода (функции) в классе. Их просто нужно всегда указывать первыми. И всё. Пока этого достаточно. При объявлении атрибута y в конструкторе класса мы использовали связку "robot.y". А в методе extrem уже использовали связку "fool.y". Таким образом, мы просто указали разные объекты внутри класса, которые работают с одной переменной y.
Рассмотрим некоторые результаты, которые получили выше. Создав экземпляр класса z, мы можем вывести на экран его атрибуты x,y и name. Выводятся они через точечную нотацию: 'имя_экземплра_класса'.'имя атрибута'.
np.set_printoptions(precision=1) # Позволяет красиво вывести на экран с заданной точностью числа
x = np.arange(1,11)
y = np.random.random(np.shape(x))
z = MyBigData(x, y) # Создаём экземпляр класса
z.extrem() # Узнаем об экстремумах вектора y
print('X data: ', z.x)
print('Y data: ', z.y)
print('Data name: ', z.name)
Ymin 0.0 Ymax 0.9 ('X data: ', array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])) ('Y data: ', array([ 0.9, 0.4, 0.8, 0.7, 0. , 0.7, 0.2, 0.7, 0.1, 0.5])) ('Data name: ', 'Sample')
Также мы вывели результат работы метода 'extrem'. Вызвав его через z.extrem(), получим на экран экстремумы выборки y.
А теперь поподробнее про функцию str c двумя подчёркиваниями с обеих сторон. Вообще говоря это относится к теме "Имитация встроенных типов в python". Это специфично и не очень нужно, поэтому я покажу вам как работает конкретно функция str c двумя подчёркиваниями с обеих сторон.
print(str(z))
print(z.__str__())
Hello, World! This is Sample data Hello, World! This is Sample data
Очевидно, что результат одинаковый! А что мы сделали? Мы взяли встроенную функцию str и применили её к экземпляру класса z. Во-втором случае мы вызывали метод в стиле ООП через точечную нотацию с использованием круглых скобок, как мы это делаем во многих других случаях. То есть использование функции с именем str с дувумя подчёркиваниями позволяет взять отнести функцию str напрямую к экземпляру класса. Зачем? Ну так быстрее в принципе. =)
Зачем нужно использование стиля ООП в моей научной и учебной работе?
Этот способ при определённом уровне мастерстве владения python, который растёт, когда вы его тренируете, оказывается удобнее и прозрачнее. Главное - определить предмет ради которого стоит создавать класс. Если у вас много однотипных данных, то создав класс, можно просто дописывать к нему методы, позволяющие получать новые результаты (данные, файлы, рисунки и т.д.).
А чем это отличается от модуля с моими пользовательскими функциями?
Тем, что в модуль, по-хорошему, нужно передавать параметры. Да, можно использовать глобальные переменные и уповать на понятливость интерпретатора python, который "видит" переменные из главного модуля (глобальные переменные), если они в нём не объявлены явно в подключаемом модули. Но этого стоит избегать. А в классе можно использовать инициализированые атрибуты в любом методе без явной передачи! Это огромная экономия и порядок!
Некоторые полезные ссылки на обучающие материалы по ООП python
Введение в объектно-ориентированное программирование (ООП) на Python
Объектно-ориентированное программирование. Общее представление