Программирование на Python

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

Разбить на страницы
Показывать лекцию целиком

Проект для лекции Lecture5.rar.

Язык Python поддерживает различные стили программирования, начиная с простейших вариантов до наиболее популярного объектно-ориентированного стиля программирования.

Работа в интерактивном режиме

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

Вот пример подобного стиля работы:

Программа Python как неделимый модуль

Язык Python позволяет написать простую программу от начала и до конца как последовательность операторов, не привлекая функций и других средств модульности. Программа выполняется оператор за оператором, в соответствии с ее текстом. Текст управляет выполнением. Чтобы написать такую программу, достаточно создать файл, представляющий модуль языка Python . Файл сохраняется с уточнением .py. Двойной щелчок на этом файле позволяет запустить модуль интерпретатором Python и выполнить программу. Для простых программ весьма удобная технология работы.

Запишем наш предыдущий пример как программу Python :

Результаты работы:

Для простых программ кроме интерпретатора ничего более не нужно. Конечно, я предпочитаю работать в среде Visual Studio, где предоставляется удобный сервис с возможностью пошаговой отладки и наблюдением за состоянием переменных, что крайне важно для более серьезных программ, чем программа данного примера.

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

Для серьезных и сложных программ могут использоваться такие технологии как функциональное, модульное, объектно-ориентированное программирование в чистом виде или как смесь технологий в одном проекте (решении). Пожалуй, только стиль логического программирования явно не поддерживается в Python .

Все стили программирования в первую очередь направлены на борьбу со сложностью программ и позволяют декомпозировать сложную программу, представляя ее в виде совокупности более простых элементов - функций в функциональном программировании, модулей - в модульном программировании, классов - в объектно-ориентированном программировании. Исторически первым появился стиль функционального программирования. Уже в первом языке программирования Fortran58 программа (routine) могла содержать подпрограммы (subroutine). В следующем языке Algol60 программа могла включать процедуры и функции. Стиль функционального программирования в этих популярных языках того времени стал общепринятым.

В чисто функциональных языках программирования, следуя математике, каждая программа P(X, Y) рассматривается как функция F: X => Y, задающая отображение данных из области определения X, содержащей входные данные, в область значений функции Y, - содержащей результаты вычислений. Декомпозиция программы F означает, что функция F представляет композицию более простых функций $$f_1, \dots f_k$$, вызывая эти функции в процессе вычисления результата функции. Каждая функция f может быть, в свою очередь, декомпозирована и представлять композицию функций. Суперпозиция - один из основных инструментов функционального программирования - предполагает, что функции могут выступать в качестве аргументов других функций.

Язык Python поддерживает стиль функционального программирования.

В языке Python функции являются объектами класса function, - такими же объектами, как и объекты других типов. В частности, они могут быть аргументами функций, которые в этом случае называются функциями высших порядков, представляя суперпозицию функций. Рассмотрим простой пример:

def f(x: float)-> float:
    return 2 * x + 1
def g(y: float)-> float:
    return y * y + 4 * y - 5
def F (x: (int, float), f : 'function', g : 'function')-> float:    """
    Функция как объект. F- Функция высших порядков - аргументы f, g являются функциями  
    """
    return f(g(x)) 
def test1():
    x = 2
    y = 3
    r1 = F(x, f, g)
    r2 = F(y, g, f)
    print('r1 = ', r1 )
    print('r2 = ', r2)
test1()

В этом примере определены три функции. Функции f и g определены над числами и возвращают число в качестве результата. Функция F - это функция высших порядков, она представляет суперпозицию функций f и g: F(x) = f(g(x)). У функции три аргумента, первый это число, второй и третий аргументы - это функции.

Функции F передать функции f и g также просто, как и число 5. Вот результат выполнения:

Рассмотрим теперь классическую содержательную задачу, требующую введения функции высшего порядка. Это задача вычисления определенного интеграла. Функция Integral, вычисляющая интеграл, имеет три параметра, - пределы интегрирования a, b и подынтегральную функцию f. Поскольку в языке Python функции являются объектами, такими же, как и, например, числа, то передавать функции Integral конкретную подынтегральную функцию в качестве фактического параметра также просто, как передать число.

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

Для вычисления площади фигуры применяется метод трапеций. Интервал интегрирования разбивается на n частей. Площадь каждой части вычисляется как площадь трапеции. В цикле выполняется удвоение n. Вычисления заканчиваются, когда площадь фигуры при n-разбиении и 2n-разбиении отличается менее чем на малую величину eps, которая задается как еще один параметр функции Integral.

Приведу реализацию этой функции:

def Integral(a, b, f, eps):
    """
    Вычисление определенного интеграла на интервале [a, b]
    с подынтегральной функцией f(x).
    Интеграл вычисляется как площадь.
    eps - точность вычислений
    """

    n = 1;    d = b - a;    s0 = -1;    h = d/2
    sf = (math.fabs(f(a)) + math.fabs(f(b)))/2
    s1 = sf * d
    while math.fabs(s1 - s0) > eps:  
        for i in range(n):
            sf += math.fabs(f(a + h + 2 * i * h))        
        s0 = s1;  s1 = sf * h; n = 2 * n;  h = h /2
    return s1

Функция Integral является функцией высших порядков, поскольку один из ее параметров является функцией. Давайте вычислим интеграл для нескольких подынтегральных функций. Две из этих функций - f и g были определены выше, третья функция является встроенной в класс math функцией sin(x):

import math
def test2():
    eps = 1e-7
    mix = Integral(0, 2, f, eps)
    print(mix)
    mix = Integral(0, 2, g, eps) 
    print(mix)
    mix = Integral(0, math.pi, math.sin, eps)
    print(mix)
test2()

Точные значения площади для всех трех случаев легко вычисляются. Результаты вычислений функции Integral с заданной точностью eps совпадают с точными значениями. Вот результаты вычислений:

Функции - константы. Лямбда определение функции

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

x = math.pi
y = 3.14

В первом присваивании используется именованная константа, во втором - константа, заданная значением. Оба способа имеют свои достоинства и недостатки. Константы, задаваемые значениями, хороши тем, что в точке их использования понятно, чему равно значение константы, что облегчает понимание данного фрагмента программы. Чтобы понять, каково значение именованной константы, например, константы pi, нужно обращаться к определению класса math.

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

lambda <кортеж имен аргументов>: 
<тело функции>   

Поскольку Python - интерпретатор, то на тело функции накладывается ограничение. Тело функции должно быть выражением, а не блоком операторов. Оно не может быть даже единичным оператором if или оператором цикла. Ограничение, конечно, можно обойти, вызвав в качестве выражения другую функцию, но тогда непонятно, зачем использовать лямбда функцию.

Когда рекомендуется применять лямбда определения? Вот типичные рекомендации:

  • Определение должно быть коротким, - значение функции - ее тело - должно занимать одну - две строчки.
  • лямбда определение одной и той же функции не должно появляться многократно.
  • Типичное применение: при вызове функции высших порядков, аргумент, представляющий функцию, задается лямбда определением. Продемонстрируем подобное использование на примере вызова функции Integral, задавая подынтегральную функцию лямбда определением:

    def test3():
        """
        Задание подынтегральной функции в точке вызова как анонимной функции
        """
        eps = 1e-7
        mix = Integral(0, 2*math.pi, lambda x: math.sin(x) + math.cos(x), eps)
        print("Интеграл от 0 до 2 * pi функции sin(x) + cos(x) = ", mix)
    test3();
    

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

    Вот результат работы:

    Страницы:

    Проект для лекции Lecture5.rar.

    Язык Python поддерживает различные стили программирования, начиная с простейших вариантов до наиболее популярного объектно-ориентированного стиля программирования.

    Работа в интерактивном режиме

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

    Вот пример подобного стиля работы:

    Программа Python как неделимый модуль

    Язык Python позволяет написать простую программу от начала и до конца как последовательность операторов, не привлекая функций и других средств модульности. Программа выполняется оператор за оператором, в соответствии с ее текстом. Текст управляет выполнением. Чтобы написать такую программу, достаточно создать файл, представляющий модуль языка Python . Файл сохраняется с уточнением .py. Двойной щелчок на этом файле позволяет запустить модуль интерпретатором Python и выполнить программу. Для простых программ весьма удобная технология работы.

    Запишем наш предыдущий пример как программу Python :

    Результаты работы:

    Для простых программ кроме интерпретатора ничего более не нужно. Конечно, я предпочитаю работать в среде Visual Studio, где предоставляется удобный сервис с возможностью пошаговой отладки и наблюдением за состоянием переменных, что крайне важно для более серьезных программ, чем программа данного примера.

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

    Для серьезных и сложных программ могут использоваться такие технологии как функциональное, модульное, объектно-ориентированное программирование в чистом виде или как смесь технологий в одном проекте (решении). Пожалуй, только стиль логического программирования явно не поддерживается в Python .

    Все стили программирования в первую очередь направлены на борьбу со сложностью программ и позволяют декомпозировать сложную программу, представляя ее в виде совокупности более простых элементов - функций в функциональном программировании, модулей - в модульном программировании, классов - в объектно-ориентированном программировании. Исторически первым появился стиль функционального программирования. Уже в первом языке программирования Fortran58 программа (routine) могла содержать подпрограммы (subroutine). В следующем языке Algol60 программа могла включать процедуры и функции. Стиль функционального программирования в этих популярных языках того времени стал общепринятым.

    В чисто функциональных языках программирования, следуя математике, каждая программа P(X, Y) рассматривается как функция F: X => Y, задающая отображение данных из области определения X, содержащей входные данные, в область значений функции Y, - содержащей результаты вычислений. Декомпозиция программы F означает, что функция F представляет композицию более простых функций $$f_1, \dots f_k$$, вызывая эти функции в процессе вычисления результата функции. Каждая функция f может быть, в свою очередь, декомпозирована и представлять композицию функций. Суперпозиция - один из основных инструментов функционального программирования - предполагает, что функции могут выступать в качестве аргументов других функций.

    Язык Python поддерживает стиль функционального программирования.

    В языке Python функции являются объектами класса function, - такими же объектами, как и объекты других типов. В частности, они могут быть аргументами функций, которые в этом случае называются функциями высших порядков, представляя суперпозицию функций. Рассмотрим простой пример:

    def f(x: float)-> float:
        return 2 * x + 1
    def g(y: float)-> float:
        return y * y + 4 * y - 5
    def F (x: (int, float), f : 'function', g : 'function')-> float:    """
        Функция как объект. F- Функция высших порядков - аргументы f, g являются функциями  
        """
        return f(g(x)) 
    def test1():
        x = 2
        y = 3
        r1 = F(x, f, g)
        r2 = F(y, g, f)
        print('r1 = ', r1 )
        print('r2 = ', r2)
    test1()
    

    В этом примере определены три функции. Функции f и g определены над числами и возвращают число в качестве результата. Функция F - это функция высших порядков, она представляет суперпозицию функций f и g: F(x) = f(g(x)). У функции три аргумента, первый это число, второй и третий аргументы - это функции.

    Функции F передать функции f и g также просто, как и число 5. Вот результат выполнения:

    Рассмотрим теперь классическую содержательную задачу, требующую введения функции высшего порядка. Это задача вычисления определенного интеграла. Функция Integral, вычисляющая интеграл, имеет три параметра, - пределы интегрирования a, b и подынтегральную функцию f. Поскольку в языке Python функции являются объектами, такими же, как и, например, числа, то передавать функции Integral конкретную подынтегральную функцию в качестве фактического параметра также просто, как передать число.

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

    Для вычисления площади фигуры применяется метод трапеций. Интервал интегрирования разбивается на n частей. Площадь каждой части вычисляется как площадь трапеции. В цикле выполняется удвоение n. Вычисления заканчиваются, когда площадь фигуры при n-разбиении и 2n-разбиении отличается менее чем на малую величину eps, которая задается как еще один параметр функции Integral.

    Приведу реализацию этой функции:

    def Integral(a, b, f, eps):
        """
        Вычисление определенного интеграла на интервале [a, b]
        с подынтегральной функцией f(x).
        Интеграл вычисляется как площадь.
        eps - точность вычислений
        """
    
        n = 1;    d = b - a;    s0 = -1;    h = d/2
        sf = (math.fabs(f(a)) + math.fabs(f(b)))/2
        s1 = sf * d
        while math.fabs(s1 - s0) > eps:  
            for i in range(n):
                sf += math.fabs(f(a + h + 2 * i * h))        
            s0 = s1;  s1 = sf * h; n = 2 * n;  h = h /2
        return s1
    

    Функция Integral является функцией высших порядков, поскольку один из ее параметров является функцией. Давайте вычислим интеграл для нескольких подынтегральных функций. Две из этих функций - f и g были определены выше, третья функция является встроенной в класс math функцией sin(x):

    import math
    def test2():
        eps = 1e-7
        mix = Integral(0, 2, f, eps)
        print(mix)
        mix = Integral(0, 2, g, eps) 
        print(mix)
        mix = Integral(0, math.pi, math.sin, eps)
        print(mix)
    test2()
    

    Точные значения площади для всех трех случаев легко вычисляются. Результаты вычислений функции Integral с заданной точностью eps совпадают с точными значениями. Вот результаты вычислений:

    Функции - константы. Лямбда определение функции

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

    x = math.pi
    y = 3.14

    В первом присваивании используется именованная константа, во втором - константа, заданная значением. Оба способа имеют свои достоинства и недостатки. Константы, задаваемые значениями, хороши тем, что в точке их использования понятно, чему равно значение константы, что облегчает понимание данного фрагмента программы. Чтобы понять, каково значение именованной константы, например, константы pi, нужно обращаться к определению класса math.

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

    lambda <кортеж имен аргументов>: 
    <тело функции>   

    Поскольку Python - интерпретатор, то на тело функции накладывается ограничение. Тело функции должно быть выражением, а не блоком операторов. Оно не может быть даже единичным оператором if или оператором цикла. Ограничение, конечно, можно обойти, вызвав в качестве выражения другую функцию, но тогда непонятно, зачем использовать лямбда функцию.

    Когда рекомендуется применять лямбда определения? Вот типичные рекомендации:

  • Определение должно быть коротким, - значение функции - ее тело - должно занимать одну - две строчки.
  • лямбда определение одной и той же функции не должно появляться многократно.
  • Типичное применение: при вызове функции высших порядков, аргумент, представляющий функцию, задается лямбда определением. Продемонстрируем подобное использование на примере вызова функции Integral, задавая подынтегральную функцию лямбда определением:

    def test3():
        """
        Задание подынтегральной функции в точке вызова как анонимной функции
        """
        eps = 1e-7
        mix = Integral(0, 2*math.pi, lambda x: math.sin(x) + math.cos(x), eps)
        print("Интеграл от 0 до 2 * pi функции sin(x) + cos(x) = ", mix)
    test3();
    

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

    Вот результат работы:

    Вернуться к учебному плану