Ленивые вычисления



бет1/4
Дата21.12.2023
өлшемі29.73 Kb.
#487228
  1   2   3   4
ЛЕНИВЫЕ ВЫЧИСЛЕНИЯ. Функторы


ЛЕНИВЫЕ ВЫЧИСЛЕНИЯ
ЛВ или отложенные вычисления (lazy evaluation) – концепция согласно которой вычисления следует откладывать до тех пор, пока не понадобится их результат.
Ленивые вычисления позволяют сократить объем вычислений, результаты которых не будут использованы. Тем самым позволяют программисту описывать только зависимости функций друг от друга и не следить за тем, чтобы не осуществлялось «лишних вычислений».
Python, не имеет встроенной поддержки ленивых вычислений, позволяет реализовать их с помощью других языковых средств.

Ленивые вычисления можно организовать в Python несколькими способами, используя различные механизмы:


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

Генераторы - функции, сохраняющие внутреннее состояние: значения локальных переменных и текущую инструкцию. При вызове генератора функция немедленно возвращает объект-итератор, который хранит текущую точку исполнения и состояние локальных переменных функции. При запросе следующего значения (посредством метода next(), неявно вызываемого в for цикле) генератор продолжает исполнение функции от предыдущей точки останова до следующего оператора yield или return.


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


sum(i for i in range(1, 100) if i % 2 != 0)

s = [x**2 for x in range(10)] - вычисление списка квадратов натуральных чисел, меньших 10.


Пример генератора:


def fib(max):
a, b = 0, 1
while a < max:
yield a
a, b = b, a + b

for n in fib(1000):


print(n, end=' ')
ФУНКТОРЫ
Функторами называют объекты, синтаксически подобные функциям, то есть поддерживающие операцию вызова. Для определения функтора нужно перегрузить оператор () с помощью метода __call__.
В Python функторы полностью аналогичны функциям, за исключением специальных атрибутов (func_code и некоторых других).
Например, функторы можно передавать в качестве функций обратного вызова (callback) в С-код. Функторы позволяют заменить некоторые приёмы, связанные с использованием замыкания, статических переменных и т. п.

Ниже представлено замыкание и эквивалентный ему функтор:


def addClosure(val1):
def closure(val2):
return val1 + val2
return closure

class AddFunctor(object):


def __init__(self, val1):
self.val1 = val1
def __call__(self, val2):
return self.val1 + val2

cl = addClosure(2)


fn = AddFunctor(2)
print cl(1), fn(1) # напечатает "3 3"

Код, использующий замыкание, будет исполняться быстрее, чем код с функтором. Это связанно с необходимостью получения атрибута val у переменной self (то есть функтор проделывает на одну Python операцию больше). Также функторы нельзя использовать для создания декораторов с параметрами.


С другой стороны, функторам доступны все возможности ООП в Python, что делает их очень полезными для функционального программирования. Например, можно написать функтор, который будет «запоминать» исполняемые над ним операции и затем повторять их. Для этого достаточно соответствующим образом перегрузить специальные методы.

class Counter:


def __init__(self):
self.__counter = 0

Мы можем создавать его экземпляры командой:


c = Counter()
Когда происходит вызов класса, автоматически запускается метод __call__ и в данном случае он создает новый экземпляр этого класса. На самом деле сначала вызывается метод __new__ для создания самого объекта в памяти устройства, а затем, метод __init__ - для его инициализации. То есть, класс можно вызывать подобно функции благодаря встроенной для него реализации метода __call__. А вот экземпляры классов так вызывать уже нельзя. Если явно в классе Counter пропишем метод __call__, например, так:

class Counter:


def __init__(self):
self.__counter = 0
def __call__(self, *args, **kwargs):
print("__call__")
self.__counter += 1
return self.__counter
то вызов экземпляра не даст ошибки:
c = Counter()
c()
c()
res = c()
print(res)
То есть, благодаря добавлению этого метода __call__в класс, можно вызывать его экземпляры подобно функциям через оператор круглые скобки. Классы, экземпляры которых можно вызывать подобно функциям, получили название функторы.
Если создать еще один объект-счетчик, то они будут работать совершенно независимо и подсчитывать число собственных вызовов:
c = Counter()
c2 = Counter()
c()
c()
res = c()
res2 = c2()
print(res, res2)

В определение метода __call__ записаны параметры *args, **kwargs. Это значит, что при вызове объектов можно передавать им произвольное количество аргументов.


def __call__(self, step=1, *args, **kwargs):


self.__counter += step
return self.__counter
Здесь появился в явном виде первый параметр step с начальным значением 1. То есть, можно вызывать объекты, например, так:

c(2)
c(10)


res = c()
res2 = c2(-5)
Пример использования класса с методом __call__ вместо замыканий функций. Можно объявить класс StripChars, который бы удалял в начале и в конце строки заданные символы:

class StripChars:


def __init__(self, chars):
self.__chars = chars
def __call__(self, *args, **kwargs):
if not isinstance(args[0], str):
raise ValueError("Аргумент должен быть строкой")
return args[0].strip(self.__chars)
Для этого, в инициализаторе сохраняем строку __chars – удаляемые символы, а затем, при вызове метода __call__ удаляем символы через строковый метод strip для символов __chars. То есть, теперь можно создать экземпляр класса и указать те символы, которые следует убирать:

s1 = StripChars("?:!.; ")


А, затем, вызвать объект s1 подобно функции:

res = s1(" Hello World! ")


print(res)
В результате объект s1 будет отвечать за удаление указанных символов в начале и конце строки. Можно определять другие объекты этого класса с другим набором символов:

s1 = StripChars("?:!.; ")


s2 = StripChars(" ")
res = s1(" Hello World! ")
res2 = s2(" Hello World! ")
print(res, res2, sep='\n')
То есть, объект s2 уже отвечает только за удаление пробелов, тогда как s1 и некоторых других символов.

ПРИМЕР ЗАМЫКАНИЯ:






Достарыңызбен бөлісу:
  1   2   3   4




©dereksiz.org 2024
әкімшілігінің қараңыз

    Басты бет