03

Парадигмы программирования

В отличии от Fortran или С которые поддерживают только парадигму процедурного программирования Python позволяет писать код также в объектно-ориентированном и функциональном стилях.

Объектно-ориентированное программировние (ООП)

ООП основанное на использовании специальных типов - классов, которые объеденяют данные и способы работы с ними.

В языке Pascal вы можете создать переменную типа string и например передать её в функцию ToUpperCase:

variable := 'AaBb'
    toUpperCase(variable)
    // Теперь variable содержит строку 'AABB'

Но приведение к верхнему регистру это операция характерная для строк, бессмысленно пытаться приводить целое число к нему. Поэтому ООП предлагает превратить функцию ToUpperCase в метод класса, иначе говоря в функцию-член класса. Рассмотрим пример:

In [20]:
a = "AaBb"
type(a)
Out[20]:
<class 'str'>

В переменную a мы сохранили строку "AaBb", которая явлется экземпляром (объектом) класса str. Теперь мы можем вызвать у переменной a один из методов класса str.

In [22]:
a.upper()
Out[22]:
'AABB'
In [23]:
# Метод может принимать аргументы (на самом деле любой метод принимает как минимум один аргумент подробнее [здесь]())
# Например посчитаем сколько раз буква A входит в строку
a.count('A')
Out[23]:
1
In [4]:
# Возвращаясь к форматированию строк, 
# то наилучшим способом будет использования метода format
"My name is {}, my age is {:d} and my temperature is {:.1f} degree.".format("Mikahil", 24, 36.6)
Out[4]:
'My name is Mikahil, my age is 24 and my temperature is 36.6 degree.'
In [10]:
a = 0.5 # Числа тоже имеют методы
a.as_integer_ratio() # Представляет вещественное число как отношение целых
Out[10]:
(1, 2)
In [14]:
dir(a) # Позволяет узнать методы, определенные у объекта,
       # методы начинающиеся с двойного подчеркивая --- служебные и обычно явно не вызываются
Out[14]:
['__abs__',
 '__add__',
 '__bool__',
 '__class__',
 '__delattr__',
 '__dir__',
 '__divmod__',
 '__doc__',
 '__eq__',
 '__float__',
 '__floordiv__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getformat__',
 '__getnewargs__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__int__',
 '__le__',
 '__lt__',
 '__mod__',
 '__mul__',
 '__ne__',
 '__neg__',
 '__new__',
 '__pos__',
 '__pow__',
 '__radd__',
 '__rdivmod__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__rfloordiv__',
 '__rmod__',
 '__rmul__',
 '__round__',
 '__rpow__',
 '__rsub__',
 '__rtruediv__',
 '__setattr__',
 '__setformat__',
 '__sizeof__',
 '__str__',
 '__sub__',
 '__subclasshook__',
 '__truediv__',
 '__trunc__',
 'as_integer_ratio',
 'conjugate',
 'fromhex',
 'hex',
 'imag',
 'is_integer',
 'real']

In-place и Out-of-place операции

Операции проводимые in place производятся непосредвенно с объектом вызвашим эту операцию, а out of place создают новый объект. Сравним два примера.

In [12]:
data = [1, 3, 2]
data.sort()
print("Список data изменился: {}".format(data))
Список data изменился: [1, 2, 3]
In [8]:
data = [ 1,3,2]
res = sorted(data)
print("Список data не изменился: {}".format(data))
print("Список res содержит результат сортировки: {}".format(res))
Список data не изменился: [1, 3, 2]
Список res содержит результат сортировки: [1, 2, 3]

В первом примере вызов метода sort отсортировал список data, во втором примере функция sorted создала новый отсртированный список оставив порядок элементов в data неизменным

Пользовательские типы (Дополнительный материал)

Однако мы можем использовать не только классы изначально сушествующие в языке, но и создавать свои!

In [45]:
class VelocityVector2D:
    """
    Класс описывающий двумерный вектор скорости
    """
    units = "м/c"
    
    def __init__(self, x, y):
        """
        Значение переменных x,y считается данным в м\с
        """
        self.x = x
        self.y = y
        
    def module(self, system_of_units = "SI"):
        """
        Модуль вектора скорости
        Аргумент system_of_units может принимать значения "SI" и "SGS"
        """
        module = (self.x**2 + self.y**2)**0.5
        if system_of_units=="SI":
            return module
        elif system_of_units=="SGS":
            return module*10 # Перевели из СИ в СГС
        else:
            raise Exception('Незнакомая система единиц')
    
    def __add__(self, another_vector):
        """
        Позволяет складывать два вектора с помощью оператора +
        """
        return VelocityVector2D(self.x + another_vector.x, self.y + another_vector.y)
    
    def __repr__(self):
        """
        Генериует строковой представление для нашего класса
        Например, мы теперь можем сделать:
        print(VelocityVector2D(3,4))
        """
        return "Vx = {0} {2}, Vy = {1} {2}".format(self.x, self.y , self.units)
    
        
In [46]:
v1 = VelocityVector2D(3,4)
In [51]:
v1.x
Out[51]:
3
In [48]:
v1.module()
Out[48]:
5.0
In [49]:
print("Модуль скорости равен {}".format(v1.module()))
Модуль скорости равен 5.0
In [50]:
v_rel = v1 + VelocityVector2D(-3,-4)
print(v_rel)
Out[50]:
Vx = 0 м/c, Vy = 0 м/c

Функциональное программирование (ФП)

Одной из основ ФП является передача функции в качестве аргумента для другой функции. Для пример реализуем функцию которая будет вычислять интеграл от произвольной функции.

In [27]:
def riman_integral(function, left_boundary = 0, rigth_boundary = 1, number_points = 200):
    """
    Вычисляем интеграл методом прямоугольников.
    Это определенно не самый лучший способ численного интегрирования,
    но самый простой и для функций без особенностей дает приемлимую точность.
    Отметим что реализация данной функции основанно на чистом Python
    и поэтому не является оптимальной по производительности.
    О высокопроизводительных вычислениях смотри раздел Numpy и Scipy
    """
    a, b = left_boundary, rigth_boundary 
    dx = (b - a) / (number_points - 1) # Интервалов на один меньше чем точек
    res = function(a)
    for i in range(1, number_points - 1):
        res += function(a + i*dx)
    res += function(b)
    return res*dx
    
In [28]:
from math import cos, pi
In [31]:
# В качестве аргумента мы передаем функцию cos от которой вычисляем интеграл
riman_integral(cos, 0, pi/2) # Вопрос на засыпку, верен ли результат?
Out[31]:
1.003941532222525

Анонимные функции (дополнительный материал)

Другим пример функционального стиля, является использование аномнимных функций. Анонимные функции могут содержать лишь одно выражение. Создаются с помощью инструкции lambda. Рассмотрим на примере такой задачи: дан набор скоростей, нужно получить набор отсортированный по модулю скорости.

In [54]:
data = [VelocityVector2D(0,0),
       VelocityVector2D(1,1),
       VelocityVector2D(0.1,0.5)]
print("Текущий порядок")
for element in data:
    print(element)
Текущий порядок
Vx = 0 м/c, Vy = 0 м/c
Vx = 1 м/c, Vy = 1 м/c
Vx = 0.1 м/c, Vy = 0.5 м/c
In [55]:
result = sorted(data, # Входные данные
                key = lambda velocity : velocity.module() # Функция принимающая в качестве аргумента элемент из data
                                                          # и определяющая порядок сортировки
               )
print("Порядок после сортировки")
for element in result:
    print(element)
Порядок после сортировки
Vx = 0 м/c, Vy = 0 м/c
Vx = 0.1 м/c, Vy = 0.5 м/c
Vx = 1 м/c, Vy = 1 м/c
In [11]:
# Анонимная функция так же может иметь несколько аргументов
print((lambda x, y: x * y)(1, 2))
2
^Наверх
Вниз