pyth_12_class_stat
Заметка 12. Классы в Питоне.
курса Математический практикум по Питону.
Шокуров Антон В.
shokurov.anton.v@yandex.ru
http://машинноезрение.рф
Версия 0.13

Аннотация

В данной заметке рассматривется такие понятия Питона как класс (мешок функций/методов и переменных). Последнее позволяет использовать в смысле наследования мощные классы относящихся к анализу данных, в частности, GenericLikelihoodModel.

Ключевые слова: Питон (python), классы (class), методы (methods), вирутальные (virtual), наследование (inheritance).

Это предварительная версия! Любые замечания приветствуются.

In [1]:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt

import scipy.stats as models

Классы в Python

In [2]:
# Сформировали массив случайных нормально распределенных чисел.
a = np.random.normal(size=100)
In [3]:
a_mean = np.mean( a ) # Выборочная средняя.
a_std = np.std( a ) # Выборочная дисперсия.

a_mean, a_std
Out[3]:
(-0.009932969582268743, 0.8662744146256866)
In [4]:
# Все тоже самое для другого массива.
b = np.random.normal(size=100)
In [5]:
b_mean = np.mean( b )
b_std = np.std( b )
b_mean, b_std
Out[5]:
(0.12365201963708301, 0.9413825011098703)
In [6]:
# Создаем класс.
class stats: # Класс по имени stats.
    def calc( self, x ): # Формируем метод класса.
        self.mean = np.mean( x ) # Результат сохраняем в объекте self.
        self.std = np.std( x ) # Объект будет хранить две величины.
In [7]:
first = stats()
stats.calc( first, a )
In [8]:
# Обе статистики теперь вместе.
print( first.mean, first.std )
-0.009932969582268743 0.8662744146256866
In [9]:
# Не спутаем со вторым объектом.
second = stats()
stats.calc( second, b )
In [10]:
print( second.mean, second.std ) # Обе статистики теперь вместе.
0.12365201963708301 0.9413825011098703

Классы на то и классы, что вызов можно сократить:

In [11]:
first.calc( a )
second.calc( b )

Печать тоже можно сделать методом

In [12]:
class stats:
    def calc( self, x ):
        self.mean = np.mean( x )
        self.std = np.std( x )
        
    def pri( self ): # Второй метод.
        print( self.mean, self.std)
In [13]:
# Попробуем вызвать.
first.pri()
# first пока является классом прошлой версии. Нужно объект заново создать.
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-13-de0f4e88fcfc> in <module>
      1 # Попробуем вызвать.
----> 2 first.pri()
      3 # first пока является классом прошлой версии. Нужно объект заново создать.

AttributeError: 'stats' object has no attribute 'pri'
In [14]:
# Объект с чистого листа.
first = stats()
stats.calc( first, a )
first.pri()
-0.009932969582268743 0.8662744146256866

Добавление элемента в объект классв.

In [15]:
# Добавили переменную в объект.
first.qq = 3 # Добавили для конкретного объекта.
In [16]:
# Проверяем значение.
first.qq
Out[16]:
3
In [17]:
# Другие объекты затронуты не будут.
second.qq
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-17-43d6b61ccda3> in <module>
      1 # Другие объекты затронуты не будут.
----> 2 second.qq

AttributeError: 'stats' object has no attribute 'qq'
In [18]:
third = stats()
In [19]:
# Создадим объект заново.
third.pri() # Отсутвуют члены класса,
# так как они не были ещё созданы.
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-19-d77df748cb78> in <module>
      1 # Создадим объект заново.
----> 2 third.pri() # Отсутвуют члены класса,
      3 # так как они не были ещё созданы.

<ipython-input-12-46984d48bc35> in pri(self)
      5 
      6     def pri( self ): # Второй метод.
----> 7         print( self.mean, self.std)

AttributeError: 'stats' object has no attribute 'mean'
In [20]:
# Можно посмотреть, что содержит сам класс.
stats.__dict__ # Под капотом...
Out[20]:
mappingproxy({'__module__': '__main__',
              'calc': <function __main__.stats.calc(self, x)>,
              'pri': <function __main__.stats.pri(self)>,
              '__dict__': <attribute '__dict__' of 'stats' objects>,
              '__weakref__': <attribute '__weakref__' of 'stats' objects>,
              '__doc__': None})
In [21]:
# В данном смысле это и мешок,
first.__dict__ # т.е. словарь сущностей в нем находящихся.
Out[21]:
{'mean': -0.009932969582268743, 'std': 0.8662744146256866, 'qq': 3}
In [22]:
second.__dict__
Out[22]:
{'mean': 0.12365201963708301, 'std': 0.9413825011098703}
In [23]:
third.__dict__ # Собственных сущностей пока не имеет.
Out[23]:
{}

Хотим добавить метод к классу

In [24]:
# Создаем функцию, которую хотим добавить к объекту.
def get_mean( self ): # Такой тип функции/метода
    return self.mean # обычно называют атрибутом.
In [25]:
first.getmm = get_mean # Присвоить удалось.
In [26]:
# Попробуем вызвать.
first.getmm() # Не получилось.
# Не хватает аргумента -- самого объекта.
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-26-9306548d8ae5> in <module>
      1 # Попробуем вызвать.
----> 2 first.getmm() # Не получилось.
      3 # Не хватает аргумента -- самого объекта.

TypeError: get_mean() missing 1 required positional argument: 'self'
In [27]:
# Нужно так: какая-то избыточность.
first.getmm( first )
Out[27]:
-0.009932969582268743
In [28]:
# Смотрим под капот. Что видим...
first.__dict__ # Видим, что наша функция
# добавилась просто как функция.
Out[28]:
{'mean': -0.009932969582268743,
 'std': 0.8662744146256866,
 'qq': 3,
 'getmm': <function __main__.get_mean(self)>}
In [29]:
# Чтобы все получилось.
from types import MethodType

Для создания метода, нужно привязать фнкцию к объекту.

In [30]:
# Используем MethodType для привязки.
first.getmm = MethodType(get_mean, first) # Первый аргумент исходная функция, а второй -- объект.
first.getmm()
Out[30]:
-0.009932969582268743
In [31]:
# Теперь видим, что getmm не функция,
first.__dict__ # а метод привязанный к объекту.
Out[31]:
{'mean': -0.009932969582268743,
 'std': 0.8662744146256866,
 'qq': 3,
 'getmm': <bound method get_mean of <__main__.stats object at 0x7f33c09c52b0>>}
In [32]:
# Чтобы увидеть, что привязка к тому объекту,
hex( id( first ) ) # что нужно.
Out[32]:
'0x7f33c09c52b0'
In [33]:
# Автоматом конечно для других объектов
second.getmm() # это не распространится.
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-33-3c2063bd0772> in <module>
      1 # Автоматом конечно для других объектов
----> 2 second.getmm() # это не распространится.

AttributeError: 'stats' object has no attribute 'getmm'
In [34]:
# Можно по аналогии добавить метод к классу.
stats.get_mm = MethodType(get_mean, stats)
In [35]:
stats.__dict__
Out[35]:
mappingproxy({'__module__': '__main__',
              'calc': <function __main__.stats.calc(self, x)>,
              'pri': <function __main__.stats.pri(self)>,
              '__dict__': <attribute '__dict__' of 'stats' objects>,
              '__weakref__': <attribute '__weakref__' of 'stats' objects>,
              '__doc__': None,
              'get_mm': <bound method get_mean of <class '__main__.stats'>>})
In [36]:
# Хм... не сработало, хотя mean в объекте есть...
first.get_mm()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-36-35a4864c9526> in <module>
      1 # Хм... не сработало, хотя mean в объекте есть...
----> 2 first.get_mm()

<ipython-input-24-639b92b2fe74> in get_mean(self)
      1 # Создаем функцию, которую хотим добавить к объекту.
      2 def get_mean( self ): # Такой тип функции/метода
----> 3     return self.mean # обычно называют атрибутом.

AttributeError: type object 'stats' has no attribute 'mean'
In [37]:
# Ничего не добавилось сюда.
first.__dict__
Out[37]:
{'mean': -0.009932969582268743,
 'std': 0.8662744146256866,
 'qq': 3,
 'getmm': <bound method get_mean of <__main__.stats object at 0x7f33c09c52b0>>}
In [38]:
first.getmm()
Out[38]:
-0.009932969582268743
In [39]:
ff = stats()
In [40]:
ff.calc(a)
In [41]:
ff.__dict__
Out[41]:
{'mean': -0.009932969582268743, 'std': 0.8662744146256866}
In [42]:
ff.get_mm()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-42-26014f7aa356> in <module>
----> 1 ff.get_mm()

<ipython-input-24-639b92b2fe74> in get_mean(self)
      1 # Создаем функцию, которую хотим добавить к объекту.
      2 def get_mean( self ): # Такой тип функции/метода
----> 3     return self.mean # обычно называют атрибутом.

AttributeError: type object 'stats' has no attribute 'mean'

конструктор

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

In [43]:
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)
In [44]:
forth = stats()
first call!
In [45]:
forth.pri() # Раньше такой вызов давал ошибку.
0 0
In [46]:
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)
In [47]:
fifth = stats()
fifth.pri()
simple call!
0 0
In [48]:
sixth = stats( a ) # Сразу подали данные.
sixth.pri()
data call!
-0.009932969582268743 0.8662744146256866

Переменная класса

Важно, что ранее объявленные переменные были имено частью self, а не класса. Иначе будет следующий эффект

In [49]:
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)
In [50]:
stats.__dict__
Out[50]:
mappingproxy({'__module__': '__main__',
              'all_means2': [],
              '__init__': <function __main__.stats.__init__(self, x=None)>,
              'calc': <function __main__.stats.calc(self, x)>,
              'calc2': <function __main__.stats.calc2(self, x)>,
              'pri': <function __main__.stats.pri(self)>,
              '__dict__': <attribute '__dict__' of 'stats' objects>,
              '__weakref__': <attribute '__weakref__' of 'stats' objects>,
              '__doc__': None})
In [51]:
first = stats( )
first.calc( a )
second = stats( )
second.calc(b )
print( first.all_means, second.all_means )
# Переменная all_means одна и таже для всех объектов.
simple call!
simple call!
[-0.009932969582268743] [0.12365201963708301]
In [52]:
first.__dict__
Out[52]:
{'mean': -0.009932969582268743,
 'std': 0.8662744146256866,
 'all_means': [-0.009932969582268743]}
In [53]:
second.__dict__
Out[53]:
{'mean': 0.12365201963708301,
 'std': 0.9413825011098703,
 'all_means': [0.12365201963708301]}
In [54]:
first.calc2( a )
second.calc2( b )
print( first.all_means2, second.all_means2 )
# Переменная all_means одна и таже для всех объектов.
[-0.009932969582268743, 0.12365201963708301] [-0.009932969582268743, 0.12365201963708301]
In [55]:
# Создаем функцию которую зотим добавить к объекту.
def get_all_mean2( self ):
    return self.all_means2
In [56]:
first.__dict__ # all_means2 нет в мешке first.
Out[56]:
{'mean': -0.009932969582268743,
 'std': 0.8662744146256866,
 'all_means': [-0.009932969582268743]}
In [57]:
stats.get_mm = MethodType(get_all_mean2, stats) # Дубль два.
In [58]:
first.get_mm() # Теперь сработало! Вывели член класса, но не объекта.
Out[58]:
[-0.009932969582268743, 0.12365201963708301]
In [59]:
first.all_means
Out[59]:
[-0.009932969582268743]

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

In [60]:
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 )#Можно и так.
In [61]:
collect.__base__
Out[61]:
__main__.stats
In [62]:
acum = collect()
# Обрати внимание на порядок печатания строк.
child init
simple call!
In [63]:
acum.__dict__
Out[63]:
{'data': [], 'mean': 0, 'std': 0, 'all_means': []}
In [64]:
acum.add( 1. )
acum.add( 2. )
In [65]:
acum.data
Out[65]:
[1.0, 2.0]
In [66]:
acum.calc_stats()
acum.pri()
1.5 0.5
In [67]:
acum.add( -2. )
acum.calc_stats()
acum.pri()
0.3333333333333333 1.699673171197595

Подмена вызова

В других языках такой метод называется виртуальным.

In [68]:
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)
In [69]:
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 )
In [70]:
acum_pos = collect()
In [71]:
acum_pos.add( 1. )
acum_pos.add( 2. )
acum_pos.add( -2. )
In [72]:
# При подсчете статистик будет вызван новый фильтр.
acum_pos.calc_stats()
acum_pos.pri() # Учитывались только положительные!
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-72-6b9dedf18694> in <module>
      1 # При подсчете статистик будет вызван новый фильтр.
----> 2 acum_pos.calc_stats()
      3 acum_pos.pri() # Учитывались только положительные!

<ipython-input-69-132b2db4b3e0> in calc_stats(self)
     15 
     16     def calc_stats( self ):
---> 17         self.calc( self.data )

<ipython-input-68-4ae87fd79a73> in calc(self, x)
     17 
     18     def calc( self, x ):
---> 19         x = self.filt2( x ) # Использует метод filt
     20         self.mean = np.mean( x ) # Мы считаем, что он есть.
     21         self.std = np.std( x )

AttributeError: 'collect' object has no attribute 'filt2'
In [73]:
acum_pos.whoami()
collect
stats
In [ ]: