2017-08-24 1 views
2

donné une fonction avec un paramètre a et deux autres paramètres (pickle_from, pickle_to), je voudrais:entrée/décorateur de sortie de résultat de la fonction de décapage

  • charge et retourner l'objet décapé situé à pickle_from, si pickle_from n'est pas None. Si c'est None, calculez une fonction de a et renvoyez-la.
  • Videz le résultat ci-dessus à pickle_to si pickle_to n'est pas None.

Avec une seule fonction, ceci est simple. Si pickle_from n'est pas nul, la fonction charge simplement le résultat picklé et le renvoie. Dans le cas contraire, il exécute un certain nombre de calculs avec a, et le renvoie à pickle_to, puis renvoie le résultat du calcul.

try: 
    import cPickle as pickle 
except: 
    import pickle 

def somefunc(a, pickle_from=None, pickle_to=None): 

    if pickle_from: 
     with open(pickle_from + '.pickle', 'rb') as f 
      res = pickle.load(f) 

    else: 
     # Re-calcualte some time-intensive func call 
     res = a ** 2 

    if pickle_to: 
     # Update pickled data with newly calculated `res` 
     with open(pickle_to + '.pickle', 'wb') as f: 
      pickle.dump(res, f) 

    return res 

Ma question porte sur la façon de construire un décorateur afin que ce processus peut former une enveloppe autour de plusieurs fonctions similaires à celles somefunc, réduisant le code source dans le processus.

Je voudrais pouvoir écrire quelque chose comme:

@pickle_option 
def somefunc(a, pickle_from=None, pickle_to=None) 
    # or do params need to be in the decorator call? 
    # remember, "the files are in the computer" 
    res = a ** 2 
    return res 

Est-ce possible? Quelque chose à propos des décorateurs fait exploser ma tête, alors je refuserai poliment de poster ici "ce que j'ai essayé".

+1

Les paramètres '' pickle_from'', 'pickle_to' et' pathname' ne devraient-ils pas être des paramètres pour le décorateur? Pourquoi 'somefunc' en a-t-il? Vous pouvez les avoir où vous voulez, mais cela affecte la façon dont le décorateur doit être codé. –

+0

Je pense qu'ils devraient être dans la fonction elle-même, car ils sont conçus pour être spécifiés par l'utilisateur plutôt que dans le code lui-même. Mais d'où ma question –

+0

@Rawing bien, tenir le coup. Si elles sont données au décorateur, il n'y a aucun moyen de changer le comportement lorsque vous appelez la fonction. Il chargera toujours ou jamais la valeur. – mwchase

Répondre

2

Ce décorateur nécessite un peu d'introspection. Plus précisément, j'ai utilisé inspect.Signature pour extraire les paramètres pickle_from et pickle_to.

Autre que cela, c'est un décorateur très simple: Il garde une référence à la fonction décorée, et l'appelle si nécessaire.

import inspect 
from functools import wraps 

def pickle_option(func): 
    sig = inspect.signature(func) 

    @wraps(func) 
    def wrapper(*args, **kwargs): 
     # get the value of the pickle_from and pickle_to parameters 
     # introspection magic, don't worry about it or read the docs 
     bound_args = sig.bind(*args, **kwargs) 
     pickle_from = bound_args.arguments.get('pickle_from', \ 
          sig.parameters['pickle_from'].default) 
     pickle_to = bound_args.arguments.get('pickle_to', \ 
          sig.parameters['pickle_to'].default) 

     if pickle_from: 
      with open(pickle_from + '.pickle', 'rb') as f: 
       result = pickle.load(f) 
     else: 
      result = func(*args, **kwargs) 

     if pickle_to: 
      with open(pickle_to + '.pickle', 'wb') as f: 
       pickle.dump(result, f) 

     return result 

    return wrapper 
+1

@BradSolomon functools.wraps devrait corriger l'inspectabilité Voir https://docs.python.org/3.6/library/functools.html#functools.wraps – mwchase

1

Compte tenu de votre cas d'utilisation, je pense qu'il serait plus clair d'utiliser juste un emballage générique:

def pickle_call(fun, *args, pickle_from=None, pickle_to=None, **kwargs): 
    if pickle_from: 
     with open(pickle_from + '.pickle', 'rb') as f 
      res = pickle.load(f) 
    else: 
     res = fun(*args, **kwargs) 
    if pickle_to: 
     # Update pickled data with newly calculated `res` 
     with open(pickle_to + '.pickle', 'wb') as f: 
      pickle.dump(res, f) 
    return res 

alors vous l'utiliser comme:

res = pickle_call(somefunc, a, pickle_from="from", pickle_to="to") 

Cela évite d'avoir à ajouter une décorateur partout où vous voulez utiliser cette fonctionnalité, et fonctionne en fait avec tout appelable (pas seulement les fonctions), à partir de votre code ou autre.

+0

Où 'res = pickle_call (somefunc ...' est spécifié dans le corps de 'somefunc'? Désolé, vous y avez suivi –

+1

@BradSolomon Non, désolé, le second extrait était destiné à montrer comment vous utiliseriez le wrapper Je suppose que vous avez défini une fonction 'somefunc' qui reçoit un paramètre' a' (comme dans votre exemple). ou bien vous voulez nommer le wrapper) et passez 'somefunc' comme premier paramètre suivi des paramètres habituels de la fonction, plus' pickle_from'/'pickle_to' si nécessaire, puis' pickle_call' n'appelle la fonction passée que si nécessaire. – jdehesa