Главная » Хабрахабр » Ленивые вычисления в быту

Ленивые вычисления в быту

И хотя людей, которые для написания списка покупок или компиляции данных по квартплате используют скрипты на python, пересчитать по головам, но если так получилось, что вы используете скрипты для решения рутинных задач и иногда скрипты работают недопустимо долго, то возможно, идея применение ленивых вычислений ко всему что движется, придётся вам по вкусу.

В предыдущих главах своего письма я дал вольное описание работы библиотеки evalcache.
Ссылка: Дисковое кэширование деревьев ленивых вычислений

Чтобы не утомлять вас необходимостью изучать тот материал перед прочтением этого, краткое изложение прошлой части:

Каждый ленивый объект имеет специальный хэш-ключ характеризуемый способом его построения. evalcache оборачивает данные, функции и методы в "ленивые объекты". Эти операции выглядят точь-в-точь, как операции над обычными данными, но в реальности никаких вычислений не производится, вместо этого выстраивается дерево ссылающихся друг на друга ленивых объектов, помнящих свои операции и их аргументы. Над ленивыми объектами могут производиться операции, приводящие к генерации новых ленивых объектов. При необходимости получения данных, производится операция раскрытия ленивого объекта, которая или приводит в действие цепочку вычислений, или подтягивает результат из кэша, если объект с таким ключем вычислялся ранее.

О некоторых изменениях

С момента написания прошлой статьи evalcache получил несколько дополнительных механик.

Механика некэшируемого исполнения

Оказывается, хэш ленивого объекта — штука настолько полезная, что хочется применять его в ситуации, когда кеширование самого объекта невозможно и ненужно.

Для этой цели введен специальный синтаксис:

lazyhash = evalcache.LazyHash()
#Эквивалентно:
#evalcache.Lazy(self, cache=None, fastdo=True) @lazyhash
def foo(): ...

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

Механика неявного раскрытия

Хотя evalcache изначально проектировался не для мемоизации, а для работы с деревьями вычислений, неявное раскрытие на основе алгоритмов evalcache может быть достигнуто. Неявное раскрытие — это ожидаемое поведение мемоизатора. onplace приводит к раскрытию ленивого объекта сразу же после создания, а onuse при попытки его использования в какой-то из допустимых для ленивого объекта операций. Для этого введены две новые опции onplace и onuse.

import evalcache lazy = evalcache.Lazy(cache=, onuse=True)
#lazy = evalcache.Lazy(cache={}, onplace=True) ### Альтернативный вариант декоратора.
#lazy = evalcache.Memoize() ### Альтернативный вариант декоратора.
#lazy = evalcache.Memoize(onplace=True) ### Альтернативный вариант декоратора. @lazy
def fib(n): if n < 2: return n return fib(n - 1) + fib(n - 2) for i in range(0,100): print(fib(i))

А о ленивых файлах: Но речь не об этой ненужном дополнении, призванном сделать библиотеку чуть более похожей на остальные ленификаторы.

Ленивые файлы

Изначально этот функционал предполагалось использовать для ленификации генерации скриншотов. Evalcache содержит надстройку для ленифицирования функций, генерирующих файлы. Как выяснилось позже, с помощью механики ленивых файлов можно делать и другие любопытные вещи.

import evalcache
import evalcache.lazyfile lazyfile = evalcache.lazyfile.LazyFile(cache = evalcache.DirCache(".evalfile")) @lazyfile(field="path")
def foo(data, path): f = open(path, "w") f.write(data) f.close() foo("HelloWorld","data.dat")

После чего применяется механика неявного раскрытия, приводящая к мгновенному запуску вычисления. Как это работает…
В целом логика работы такая же, как и у всех ленивых объектов.
foo("HelloWorld","data.dat") Начинает конструирование ленивого объекта хэш-ключ которого завязан на передаваемые ему аргументы.

Декоратор ожидает, что по факту выполнения функции, по этому пути будет создан файл. Но дальше порядок действий меняется.
@lazyfile(field="path") Декоратор lazyfile анализирует параметр, с название указанным в field. Имя файла жесткой ссылки соответствует хэшключу ленивого объекта. evalcache берет этот этот файл и создаёт на него жесткую ссылку в директории хэша ".evalfile". В дальнейшем при нахождении в кэше файла с таким именем, evalcache при раскрытии объекта вместо вызова функции, просто создат в требуемом месте жесткую ссылку на существующий в кэше файл.

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

import evalcache
import evalcache.lazyfile lazy = evalcache.lazy.Lazy(cache = evalcache.DirCache(".evalcache"))
lazyfile = evalcache.lazyfile.LazyFile(cache = evalcache.DirCache(".evalfile")) @lazyfile(field="path")
def foo(data, path): f = open(path, "w") f.write(data) f.close() @lazy
def datagenerator(): return "HelloWorld" foo(datagenerator(),"data.dat")

Применение ленификации к монтажу видео через инструмент moviepy.

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

Монтаж — пожалуйста. В частности библиотека moviepy и два часа изучения документации к ней дают нам в руки простой и функциональный видеоредактор. Спецэфекты — пожалуйста. Звук наложить — пожалуйста.

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

Применение библиотеки evalcache помогло внести коррективы в эту ситуацию.

#!/usr/bin/env python3
#coding:utf-8 import sys
import types from moviepy.editor import *
import evalcache.lazyfile lazyhash = evalcache.LazyHash()
lazyfile = evalcache.lazyfile.LazyFile() LazyVideoClip = lazyhash(VideoClip)
VideoFileClip = lazyhash(VideoFileClip)
AudioFileClip = lazyhash(AudioFileClip)
CompositeVideoClip = lazyhash(CompositeVideoClip)
concatenate_videoclips = lazyhash(concatenate_videoclips) @lazyfile("path")
def lazy_write_videofile(path, clip): clip.write_videofile(path) source = VideoFileClip("source.mp4")
music0 = AudioFileClip("music0.mp3")
music1 = AudioFileClip("music1.mp3") music = music0
dur = 3
s0 = 1
s1 = 8
s2 = 16 part0 = source.subclip(s0, s0 + dur)
part1 = source.subclip(s1, s1 + dur * 2).fl_time(lambda t: t * 2).set_duration(2)
part2 = source.subclip(s2, s2 + dur * 4).fl_time(lambda t: t * 5).set_duration(2) clip = concatenate_videoclips([part0, part1, part2])
clip = clip.set_audio(music.set_duration(clip.duration)) lazy_write_videofile("part0.mp4", part0)
lazy_write_videofile("part1.mp4", part1)
lazy_write_videofile("part2.mp4", part2)
lazy_write_videofile("part0_mus.mp4", part0.set_audio(music.set_duration(part0.duration)))
lazy_write_videofile("part1_mus.mp4", part1.set_audio(music.set_duration(part1.duration)))
lazy_write_videofile("part2_mus.mp4", part2.set_audio(music.set_duration(part2.duration))) if len(sys.argv) > 1 and sys.argv[1] == "compile": clip.lazy_write_videofile("clip.mp4", clip)

Что здесь есть.

При этом мы получаем все преимущества ленивых объектов для отслеживания изменений в дереве исполнения. Мы применяем механику некэшируемого исполнения, так как разбираться с кэшированием объектов moviepy никакого желания и необходимости нет.

Оборачиваем вызовы библиотеки в ленификаторы:

LazyVideoClip = lazyhash(VideoClip)
VideoFileClip = lazyhash(VideoFileClip)
AudioFileClip = lazyhash(AudioFileClip)
CompositeVideoClip = lazyhash(CompositeVideoClip)
concatenate_videoclips = lazyhash(concatenate_videoclips)

С помощью этой конструкции будем генерировать файлы:

@lazyfile("path")
def lazy_write_videofile(path, clip): clip.write_videofile(path)

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

lazy_write_videofile("part0.mp4", part0)
lazy_write_videofile("part1.mp4", part1)
lazy_write_videofile("part2.mp4", part2)
lazy_write_videofile("part0_mus.mp4", part0.set_audio(music.set_duration(part0.duration)))
lazy_write_videofile("part1_mus.mp4", part1.set_audio(music.set_duration(part1.duration)))
lazy_write_videofile("part2_mus.mp4", part2.set_audio(music.set_duration(part2.duration)))

Эти файлы будут перекомпилироваться только при изменении соответствующим им веток исполнения.

Когда результат сложился, собираем части в большой файл, используя опцию 'compile':

if len(sys.argv) > 1 and sys.argv[1] == "compile": clip.lazy_write_videofile("clip.mp4", clip)

Вместо заключения:

В настоящем тексте показано, как с помощью библиотеки evalcache, можно ленифицировать алгоритм, предполагающей генерацию файлов.
Такой подход позволяет снизить зависимость от специализированного софта или избежать написания сложной логики избирательной сборки.

Ссылки:

Проект на github
Проект на pypi


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

У нас DevOps. Давайте уволим всех тестировщиков

Можно ли автоматизировать всё, что угодно? Потом всех тестировщиков уволим, конечно. Зачем они теперь нужны, «ручного» тестирования не осталось. Правильно ведь? Здесь будут конкретные цифры и чисто практические выводы, как так получается, что у хороших специалистов всегда есть работа. Это ...

Современный PHP — прекрасен и продуктивен

Почти 8 месяцев тому назад я пересел с проектов python/java на проект на php (мне предложили условия от которых было бы глупо отказываться), и я внезапно не ощутил боли и отчаяния, о которых проповедуют бывшие разработчики на ПХП. И вот ...