C'est un processus courant pour traiter des données, les enregistrer une fois sur un disque et les réutiliser (ignorer le traitement des données à partir de la deuxième fois), mais en tenant compte de la dépendance des paramètres au moment de la réutilisation, etc. Cela a tendance à être compliqué de manière inattendue. Par conséquent, considérez une implémentation qui ne répète pas le même processus en faisant un jugement de saut à l'aide d'un décorateur Python.
Une bibliothèque de ce processus peut être trouvée sur github.com/sotetsuk/memozo:
Par exemple, supposons que vous ayez maintenant une énorme quantité de données de déclaration (une phrase par ligne):
1. I have a pen.
2. I have an apple.
3. ah! Apple pen!
...
9999...
# PPAP (copyright belongs to Pikotaro)
Supposons maintenant que vous souhaitiez filtrer uniquement les phrases contenant un mot-clé spécifique à partir de ces données (par exemple, la phrase qui contient le mot-clé
pen '').
L'une des implémentations naïves du filtre serait de créer un générateur qui cède à chaque fois qu'il trouve une instruction qui répond aux critères:
def filter_data(keyword):
path_to_raw_data = './data/sentences.txt'
with codecs.open(path_to_raw_data, 'r', 'utf-8') as f:
for line in f:
if keyword in line:
yield line
gen = filter_data('pen')
for line in gen:
print(line, end='')
Et si ces données traitées (données filtrées) sont réutilisées plusieurs fois, ce n'est pas toujours une bonne idée de scanner toutes les données à chaque fois.
Vous souhaiterez peut-être mettre en cache les données filtrées sur le disque une fois, puis utiliser les données mises en cache.
De plus, ce processus de traitement des données dépend du paramètre (mot-clé```), donc si ce processus est exécuté avec un
mot-clé '' différent, toutes les données seront à nouveau vérifiées et placées sur le disque. Il y a aussi l'aspect de vouloir mettre en cache.
Et j'ai le désir de réaliser ce processus simplement en enveloppant la fonction à l'aide d'un décorateur.
En résumé, le but est d'utiliser le décorateur awesome_decorator
pour mettre en cache la sortie du générateur, et si cette fonction est exécutée avec les mêmes paramètres, utilisez le cache pour renvoyer la sortie: est:
@awesome_decorator
def filter_data(keyword):
path_to_raw_data = './data/sentences.txt'
with codecs.open(path_to_raw_data, 'r', 'utf-8') as f:
for line in f:
if keyword in line:
yield line
#La première fois, il analyse toutes les données et renvoie le résultat.
#À ce stade, l'instruction filtrée'./data/pen.txt'Cachez sur.
gen_pen_sentences1 = filter_data('pen')
for line in gen_pen_sentences1:
print(line, end='')
#Puisqu'il est exécuté avec les mêmes paramètres, le cache'./data/pen.txt'Renvoie les données de.
gen_pen_sentences2 = filter_data('pen')
for line in gen_pen_sentences2:
print(line, end='')
#Puisqu'il s'agit d'un nouveau paramètre, nous le filtrerons à nouveau à partir des données brutes.
gen_apple_sentences = filter_data('apple')
for line in gen_apple_sentences:
print(line, end='')
De plus, cet exemple est une fonction qui renvoie un générateur, mais il peut y avoir d'autres situations où vous souhaitez mettre en cache le résultat de l'exécution d'une fonction qui retourne un objet qui peut être sérialisé par pickle '' sur le disque (par exemple, prétraité).
ndarray '' et modèles d'apprentissage automatique formés en fonction des paramètres).
awesome_decorator
Est facile à mettre en œuvre, déterminez s'il existe déjà des fichiers en cache,
Juste (même si vous utilisez `` pickle '', etc.):
def awesome_decorator(func):
@functools.wraps(func)
def _wrapper(keyword):
#Cette fois, par souci de simplicité, nous supposons que l'argument de la fonction n'est qu'un mot clé.
#général(*args, **kwargs)Lors de l'utilisation, utilisez inspect etc. pour extraire les arguments et leurs valeurs.
file_path = './data/{}.txt'.format(keyword)
#S'il y a des données en cache, il renvoie un générateur qui lit les instructions à partir de celui-ci.
if os.path.exists(file_path):
def gen_cached_data():
with codecs.open(file_path, 'r', 'utf-8') as f:
for line in f:
yield line
return gen_cached_data()
#S'il n'y a pas de données mises en cache, il générera un décorateur qui retournera une instruction à partir des données brutes comme d'habitude.
gen = func(keyword)
#Il met également en cache les valeurs renvoyées par les générateurs ci-dessus.
def generator_with_cache(gen, file_path):
with codecs.open(file_path, 'w', 'utf-8') as f:
for e in gen:
f.write(e)
yield e
return generator_with_cache(gen, file_path)
return _wrapper
Pour une explication du décorateur lui-même, l'article 12 étapes pour comprendre les décorateurs Python est facile à comprendre.
Dans l'ensemble, cela ressemble à ceci (cela fonctionne très bien avec
. / Data / phrase.txt ''):
awesome_generator.py
# -*- coding: utf-8 -*-
import os
import functools
import codecs
def awesome_decorator(func):
@functools.wraps(func)
def _wrapper(keyword):
#Cette fois, par souci de simplicité, nous supposons que l'argument de la fonction n'est qu'un mot clé.
#général(*args, **kwargs)Lors de l'utilisation, utilisez inspect etc. pour extraire les arguments et leurs valeurs.
file_path = './data/{}.txt'.format(keyword)
#S'il y a des données en cache, il renvoie un générateur qui lit les instructions à partir de celui-ci.
if os.path.exists(file_path):
def gen_cached_data():
with codecs.open(file_path, 'r', 'utf-8') as f:
for line in f:
yield line
return gen_cached_data()
#S'il n'y a pas de données mises en cache, il générera un décorateur qui retournera une instruction à partir des données brutes comme d'habitude.
gen = func(keyword)
#Il met également en cache les valeurs renvoyées par les générateurs ci-dessus.
def generator_with_cache(gen, file_path):
with codecs.open(file_path, 'w', 'utf-8') as f:
for e in gen:
f.write(e)
yield e
return generator_with_cache(gen, file_path)
return _wrapper
@awesome_decorator
def filter_data(keyword):
path_to_raw_data = './data/sentences.txt'
with codecs.open(path_to_raw_data, 'r', 'utf-8') as f:
for line in f:
if keyword in line:
yield line
if __name__ == '__main__':
#La première fois, il analyse toutes les données et renvoie le résultat.
#À ce stade, l'instruction filtrée'./data/pen.txt'Cachez sur.
gen_pen_sentences1 = filter_data('pen')
for line in gen_pen_sentences1:
print(line, end='')
#Puisqu'il est exécuté avec les mêmes paramètres, le cache'./data/pen.txt'Renvoie les données de.
gen_pen_sentences2 = filter_data('pen')
for line in gen_pen_sentences2:
print(line, end='')
#Puisqu'il s'agit d'un nouveau paramètre, nous le filtrerons à nouveau à partir des données brutes.
gen_apple_sentences = filter_data('apple')
for line in gen_apple_sentences:
print(line, end='')
memozo 今回の実装は,パラメータの形やファイル名等を固定された形で扱っていましたが,任意の形に少し拡張したものをパッケージとしてgithub.com/sotetsuk/memozoにまとめました. Avec cela, ce processus peut être écrit comme ceci:
from memozo import Memozo
m = Memozo('./data')
@m.generator(file_name='filtered_sentences', ext='txt')
def filter_data(keyword):
path_to_raw_data = './data/sentences.txt'
with codecs.open(path_to_raw_data, 'r', 'utf-8') as f:
for line in f:
if keyword in line:
yield line
Le fichier cache est enregistré dans '' ./ data / filtered_sentences_1fec01f.txt'`
, et l'historique des paramètres utilisés dans
. / Data / .memozo``` est écrit.
Le hachage est calculé à partir de (nom du fichier, nom de la fonction, paramètre), et si l'historique et le fichier cache utilisant le même hachage existent déjà, l'exécution de la fonction sera ignorée.
En d'autres termes, si vous exécutez avec le même (nom de fichier, nom de fonction, paramètre), la valeur sera renvoyée depuis le cache, et si vous en changez une, le résultat sera différent.
En plus du générateur, il existe des versions de fonctions qui correspondent à pickle```,
codecs``` et ordinaire
open```.
Je pense que la mise en œuvre est encore incomplète, donc je vous serais reconnaissant de bien vouloir mentionner Problème / PR, etc.
タスク間に複雑な依存関係がある場合はDAGベースのワークフローツールを使った方がいいでしょう.一例として,github.com/spotify/luigiなどが挙げられます.
Recommended Posts