Математический практикум по Питону.
В данной заметке рассматривется такие понятия Питона как класс (мешок функций/методов и переменных). Последнее позволяет использовать в смысле наследования мощные классы относящихся к анализу данных, в частности, GenericLikelihoodModel.
Ключевые слова: Питон (python), классы (class), методы (methods), вирутальные (virtual), наследование (inheritance).
Это предварительная версия! Любые замечания приветствуются.
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import scipy.stats as models
# Сформировали массив случайных нормально распределенных чисел.
a = np.random.normal(size=100)
a_mean = np.mean( a ) # Выборочная средняя.
a_std = np.std( a ) # Выборочная дисперсия.
a_mean, a_std
# Все тоже самое для другого массива.
b = np.random.normal(size=100)
b_mean = np.mean( b )
b_std = np.std( b )
b_mean, b_std
# Создаем класс.
class stats: # Класс по имени stats.
def calc( self, x ): # Формируем метод класса.
self.mean = np.mean( x ) # Результат сохраняем в объекте self.
self.std = np.std( x ) # Объект будет хранить две величины.
first = stats()
stats.calc( first, a )
# Обе статистики теперь вместе.
print( first.mean, first.std )
# Не спутаем со вторым объектом.
second = stats()
stats.calc( second, b )
print( second.mean, second.std ) # Обе статистики теперь вместе.
Классы на то и классы, что вызов можно сократить:
first.calc( a )
second.calc( b )
Печать тоже можно сделать методом
class stats:
def calc( self, x ):
self.mean = np.mean( x )
self.std = np.std( x )
def pri( self ): # Второй метод.
print( self.mean, self.std)
# Попробуем вызвать.
first.pri()
# first пока является классом прошлой версии. Нужно объект заново создать.
# Объект с чистого листа.
first = stats()
stats.calc( first, a )
first.pri()
Добавление элемента в объект классв.
# Добавили переменную в объект.
first.qq = 3 # Добавили для конкретного объекта.
# Проверяем значение.
first.qq
# Другие объекты затронуты не будут.
second.qq
third = stats()
# Создадим объект заново.
third.pri() # Отсутвуют члены класса,
# так как они не были ещё созданы.
# Можно посмотреть, что содержит сам класс.
stats.__dict__ # Под капотом...
# В данном смысле это и мешок,
first.__dict__ # т.е. словарь сущностей в нем находящихся.
second.__dict__
third.__dict__ # Собственных сущностей пока не имеет.
Хотим добавить метод к классу
# Создаем функцию, которую хотим добавить к объекту.
def get_mean( self ): # Такой тип функции/метода
return self.mean # обычно называют атрибутом.
first.getmm = get_mean # Присвоить удалось.
# Попробуем вызвать.
first.getmm() # Не получилось.
# Не хватает аргумента -- самого объекта.
# Нужно так: какая-то избыточность.
first.getmm( first )
# Смотрим под капот. Что видим...
first.__dict__ # Видим, что наша функция
# добавилась просто как функция.
# Чтобы все получилось.
from types import MethodType
Для создания метода, нужно привязать фнкцию к объекту.
# Используем MethodType для привязки.
first.getmm = MethodType(get_mean, first) # Первый аргумент исходная функция, а второй -- объект.
first.getmm()
# Теперь видим, что getmm не функция,
first.__dict__ # а метод привязанный к объекту.
# Чтобы увидеть, что привязка к тому объекту,
hex( id( first ) ) # что нужно.
# Автоматом конечно для других объектов
second.getmm() # это не распространится.
# Можно по аналогии добавить метод к классу.
stats.get_mm = MethodType(get_mean, stats)
stats.__dict__
# Хм... не сработало, хотя mean в объекте есть...
first.get_mm()
# Ничего не добавилось сюда.
first.__dict__
first.getmm()
ff = stats()
ff.calc(a)
ff.__dict__
ff.get_mm()
конструктор
Объект начинает свою жизнь с вызова конструктора. Это позволяет построить корретный объект. Так, в нем принято присваивать переменным объекта предопределенные значения.
class stats:
def __init__( self ):# Конструктор!
print('first call!')
self.mean = 0 # Сразу есть,
self.std = 0 # до вызова calc.
def calc( self, x ):
self.mean = np.mean( x )
self.std = np.std( x )
def pri( self ):
print( self.mean, self.std)
forth = stats()
forth.pri() # Раньше такой вызов давал ошибку.
class stats:
def __init__( self, x = None ):# Конструкотор,
if x is None: # расчитан на два случая.
print('simple call!')# Данных нет.
self.mean = 0
self.std = 0
return
print('data call!') # Данные поданы.
self.calc( x )
def calc( self, x ):
self.mean = np.mean( x )
self.std = np.std( x )
def pri( self ):
print( self.mean, self.std)
fifth = stats()
fifth.pri()
sixth = stats( a ) # Сразу подали данные.
sixth.pri()
Переменная класса
Важно, что ранее объявленные переменные были имено частью self, а не класса. Иначе будет следующий эффект
class stats:
all_means2 = []
def __init__( self, x = None ):
if x is None:
print('simple call!')
self.mean = 0
self.std = 0
else:
print('data call!')
self.all_means = []
def calc( self, x ):
self.mean = np.mean( x )
self.std = np.std( x )
self.all_means.append( self.mean )
def calc2( self, x ):
# Используем ранее вычисленное значение.
self.all_means2.append( self.mean )
def pri( self ):
print( self.mean, self.std)
stats.__dict__
first = stats( )
first.calc( a )
second = stats( )
second.calc(b )
print( first.all_means, second.all_means )
# Переменная all_means одна и таже для всех объектов.
first.__dict__
second.__dict__
first.calc2( a )
second.calc2( b )
print( first.all_means2, second.all_means2 )
# Переменная all_means одна и таже для всех объектов.
# Создаем функцию которую зотим добавить к объекту.
def get_all_mean2( self ):
return self.all_means2
first.__dict__ # all_means2 нет в мешке first.
stats.get_mm = MethodType(get_all_mean2, stats) # Дубль два.
first.get_mm() # Теперь сработало! Вывели член класса, но не объекта.
first.all_means
Наследование
class collect( stats ): # Дитя.
def __init__(self):
self.data = []
print('child init')
super( collect, self).__init__()
def add( self, x ):
self.data.append( x )
def calc_stats( self ):
self.calc( self.data )
#super( collect, self).calc( self.data )#Можно и так.
collect.__base__
acum = collect()
# Обрати внимание на порядок печатания строк.
acum.__dict__
acum.add( 1. )
acum.add( 2. )
acum.data
acum.calc_stats()
acum.pri()
acum.add( -2. )
acum.calc_stats()
acum.pri()
Подмена вызова
В других языках такой метод называется виртуальным.
class stats:
all_means = []
def __init__( self, x = None ):
if x is None:
print('simple call!')
self.mean = 0
self.std = 0
return
print('data call!')
self.calc( x )
#def filt( self, x): # Данного метода нет!
# return x
def whoami( self ):
print('stats')
def calc( self, x ):
x = self.filt2( x ) # Использует метод filt
self.mean = np.mean( x ) # Мы считаем, что он есть.
self.std = np.std( x )
self.all_means.append( self.mean )
def pri( self ):
print( self.mean, self.std)
class collect( stats ):
def __init__(self):
self.data = []
def filt( self, x): # Новый фильтр. Он подменяет из родительского.
xx = list(filter( lambda x: x>0, x))
return xx
def whoami(self):
print('collect')
super(collect, self).whoami()
def add( self, x ):
self.data.append( x )
def calc_stats( self ):
self.calc( self.data )
acum_pos = collect()
acum_pos.add( 1. )
acum_pos.add( 2. )
acum_pos.add( -2. )
# При подсчете статистик будет вызван новый фильтр.
acum_pos.calc_stats()
acum_pos.pri() # Учитывались только положительные!
acum_pos.whoami()