2010-07-31 18 views
10

Est-ce une "bonne pratique" de créer une classe comme celle ci-dessous qui peut gérer le processus de mémo pour vous? Les avantages de la mémo sont si grands (dans certains cas, comme celui-ci, où il passe de 501003 à 1507 appels de fonction et de 1.409 à 0.006 secondes de temps CPU sur mon ordinateur) qu'il semble que ce serait utile.Gestionnaire de mémo

Cependant, je n'ai lu que des commentaires négatifs sur l'utilisation de eval(). Cet usage est-il excusable, étant donné la flexibilité qu'offre cette approche?

Ceci peut sauver n'importe quelle valeur retournée automatiquement au prix de perdre des effets secondaires. Merci.

import cProfile 

class Memoizer(object): 
    """A handler for saving function results.""" 
    def __init__(self): 
     self.memos = dict() 
    def memo(self, string): 
     if string in self.memos: 
      return self.memos[string] 
     else: 
      self.memos[string] = eval(string) 
      self.memo(string) 

def factorial(n): 
    assert type(n) == int 
    if n == 1: 
     return 1 
    else: 
     return n * factorial(n-1) 

# find the factorial of num 
num = 500 
# this many times 
times = 1000 

def factorialTwice(): 
    factorial(num) 
    for x in xrange(0, times): 
     factorial(num) 
    return factorial(num) 

def memoizedFactorial(): 
    handler = Memoizer() 
    for x in xrange(0, times): 
     handler.memo("factorial(%d)" % num) 
    return handler.memo("factorial(%d)" % num) 


cProfile.run('factorialTwice()') 

cProfile.run('memoizedFactorial()') 
+0

Vous parlez de "décorateurs Python" et memoization est une utilisation fantastique pour eux. Et cela ne nécessite pas d'Eval (qui sont partiellement mauvais, vous avez bien entendu). – msw

Répondre

13

Vous pouvez mémoriser sans avoir recours à eval.

A (très basique) memoizer:

def memoized(f): 
    cache={} 
    def ret(*args): 
     if args in cache: 
      return cache[args] 
     else: 
      answer=f(*args) 
      cache[args]=answer 
      return answer 
    return ret 

@memoized 
def fibonacci(n): 
    if n==0 or n==1: 
     return 1 
    else: 
     return fibonacci(n-1)+fibonacci(n-2) 

print fibonacci(100) 
5

evalest souvent mal orthographié comme evil principalement parce que l'idée d'exécuter "chaînes" lors de l'exécution se heurte à des considérations de sécurité. Avez-vous échappé le code suffisamment? Guillemets? Et une foule d'autres maux de tête ennuyeux. Votre gestionnaire de memoise fonctionne mais ce n'est vraiment pas la façon de faire les choses en Python. L'approche de MAK est beaucoup plus pythonique. Essayons quelques expériences.

J'ai édité les deux versions et les ai fait fonctionner une seule fois avec 100 comme entrée. J'ai également déplacé l'instanciation de Memoizer. Voici les résultats.

>>> timeit.timeit(memoizedFactorial,number=1000) 
0.08526921272277832h 
>>> timeit.timeit(foo0.mfactorial,number=1000) 
0.000804901123046875 

En plus de cela, votre version nécessite une enveloppe autour de la fonction à memoised qui doit être écrit dans une chaîne. C'est moche. La solution de MAK est propre puisque le "processus de mémoiation" est encapsulé dans une fonction séparée qui peut être commodément appliquée à n'importe quelle fonction coûteuse d'une manière discrète. Ce n'est pas très Pythonien. J'ai quelques détails sur l'écriture de ces décorateurs dans mon tutoriel Python au http://nibrahim.net.in/self-defence/ au cas où vous êtes intéressé.