Question

J'essaie d'écrire un décorateur Freeze pour Python.

L'idée est la suivante:

(En réponse aux deux commentaires)

Je peux me tromper mais je pense qu’il ya deux utilisations principales: cas de test.

  • L'un est le développement piloté par les tests: Idéalement, les développeurs écrivent des cas avant d'écrire le code. Il est généralement utile de définir l’architecture car cette discipline forces pour définir les interfaces réelles avant le développement. On peut même considérer que dans certains cas, la personne qui répartit le travail entre dev écrit le cas de test et utilisez-le pour illustrer efficacement la spécification qu'il a en tête. Je n'ai aucune expérience de l'utilisation d'un cas de test comme celui-ci.

  • La seconde est l’idée que tous projettent avec décente taille et plusieurs programmeurs souffrent de code cassé. Quelque chose qui travaillait peut être brisé par un changement cela ressemblait à un innocent refactoring. Bien que la bonne architecture, un couple lâche entre les composants peut aider à lutter contre ce phénomène; tu dormiras mieux la nuit si vous avez écrit un cas de test pour vous assurer que rien ne casse le comportement de votre programme.

TOUTEFOIS, Personne ne peut nier les frais généraux liés à la rédaction de tests. dans le premier cas, on peut soutenir que le test est en fait guider développement et ne doit donc pas être considéré comme une surcharge.

Franchement, je suis un jeune programmeur et si j’étais vous, ma parole à ce sujet n'a pas vraiment de valeur ... En tout cas, je pense que la plupart des sociétés / projets ne fonctionnent pas comme ça, et que les tests unitaires sont principalement utilisés dans la seconde cas ...

En d'autres termes, plutôt que de s'assurer que le programme est fonctionne correctement, il vise à vérifier qu’il sera travailler de la même manière à l'avenir.

Ce besoin peut être satisfait sans le coût de la rédaction des tests, en utilisant ce décorateur de congélation.

Disons que vous avez une fonction

def pow(n,k):
    if n == 0:  return 1
    else:       return n * pow(n,k-1)

Il est parfaitement agréable et vous souhaitez le réécrire sous forme de version optimisée. Cela fait partie d'un grand projet. Vous voulez qu'il donne le même résultat pour un peu de valeur. Plutôt que de passer par la douleur des cas de test, on pourrait utiliser certaines genre de décorateur de gel.

Quelque chose de tel que la première fois que le décorateur est exécuté, le décorateur exécute la fonction avec les arguments définis (inférieurs à 0 et 7) et enregistre le résultat dans une carte (f - > args - > result)

@freeze(2,0)
@freeze(1,3)
@freeze(3,5)
@freeze(0,0)
def pow(n,k):
    if n == 0:  return 1
    else:       return n * pow(n,k-1)

La prochaine fois que le programme sera exécuté, le décorateur chargera cette carte et vérifiera que le résultat de cette fonction pour ces arguments n’a pas changé.

J’ai déjà écrit rapidement le décorateur (voir ci-dessous), mais j'ai eu quelques problèmes avec dont j'ai besoin de vos conseils ...

from __future__ import with_statement
from collections import defaultdict
from types import GeneratorType
import cPickle

def __id_from_function(f):
    return ".".join([f.__module__, f.__name__])

def generator_firsts(g, N=100):
    try:
        if N==0:
            return []
        else:
            return  [g.next()] + generator_firsts(g, N-1)
    except StopIteration :
        return []

def __post_process(v):
    specialized_postprocess = [
        (GeneratorType, generator_firsts),
        (Exception,     str),
    ]
    try:
        val_mro = v.__class__.mro()
        for ( ancestor, specialized ) in specialized_postprocess:
            if ancestor in val_mro:
                return specialized(v)
        raise ""
    except:
        print "Cannot accept this as a value"
        return None

def __eval_function(f):
    def aux(args, kargs):
        try:
            return ( True, __post_process( f(*args, **kargs) ) )
        except Exception, e:
            return ( False, __post_process(e) )
    return aux

def __compare_behavior(f, past_records):
    for (args, kargs, result) in past_records:
        assert __eval_function(f)(args,kargs) == result

def __record_behavior(f, past_records, args, kargs):
    registered_args = [ (a, k) for (a, k, r) in past_records ]
    if (args, kargs) not  in registered_args:
        res = __eval_function(f)(args, kargs)
        past_records.append( (args, kargs, res) )

def __open_frz():
    try:
        with open(".frz", "r") as __open_frz:
            return cPickle.load(__open_frz)
    except:
        return defaultdict(list)

def __save_frz(past_records):
    with open(".frz", "w") as __open_frz:
        return cPickle.dump(past_records, __open_frz)


def freeze_behavior(*args, **kvargs):
    def freeze_decorator(f):
        past_records = __open_frz()
        f_id = __id_from_function(f)
        f_past_records = past_records[f_id]
        __compare_behavior(f, f_past_records)
        __record_behavior(f, f_past_records, args, kvargs)
        __save_frz(past_records)
        return f
    return freeze_decorator
  • Le dumping et la comparaison des résultats ne sont pas triviaux pour tous les types. En ce moment, je pense à utiliser une fonction (je l’appelle post-traitement ici), pour résoudre ce problème. Fondamentalement, au lieu de stocker res, je stocke postprocess (res) et je compare postprocess (res1) == postprocess (res2), au lieu de comparer res1 res2. Il est important de laisser l'utilisateur surcharger la fonction de post-traitement prédéfinie. Ma première question est: Connaissez-vous un moyen de vérifier si un objet peut être déposé ou non?

  • Définir une clé pour la fonction décorée est une douleur. Dans les extraits suivants J'utilise le module fonction et son nom. ** Pouvez-vous penser à une façon plus intelligente de le faire? **

  • Les extraits ci-dessous fonctionnent assez bien, mais ouvrent et ferment le fichier lors des tests et lors de l'enregistrement. Ceci est juste un prototype stupide ... mais connaissez-vous un bon moyen d’ouvrir le fichier, de traiter le décorateur pour toutes les fonctions, de fermer le fichier ...

  • Je compte y ajouter quelques fonctionnalités. Par exemple, ajoutez la possibilité de définir iterable pour parcourir un ensemble d'arguments, enregistrer des arguments d'utilisation réelle, etc. Pourquoi vous attendriez-vous d'un tel décorateur?

  • En général, utiliseriez-vous une telle fonctionnalité,

Était-ce utile?

La solution

"En général, utiliseriez-vous une telle fonctionnalité, connaissant ses limites ...?"

Franchement, jamais.

Il n’existe aucune circonstance dans laquelle je voudrais "geler" les résultats d'une fonction de cette manière.

Le cas d'utilisation semble reposer sur deux idées fausses: (1) les tests unitaires sont difficiles, complexes ou coûteux; et (2) il pourrait être plus simple d'écrire le code, " freeze " les résultats et en quelque sorte utiliser les résultats gelés pour la refactorisation. Ce n'est pas utile. En effet, la très réelle possibilité de geler les mauvaises réponses en fait une mauvaise idée.

Premièrement, sur "cohérence vs exactitude". C’est plus facile à conserver avec un simple mappage qu’avec un ensemble complexe de décorateurs.

Faites ceci au lieu d’écrire un décorateur Freeze.

print "frozen_f=", dict( (i,f(i)) for i in range(100) )

L'objet dictionnaire créé fonctionnera parfaitement en tant que jeu de résultats figé. Pas de décorateur. Aucune complexité à proprement parler.

Deuxièmement, sur les "tests unitaires".

Le but d'un test unitaire n'est pas de "geler". des résultats aléatoires. L'intérêt d'un test unitaire est de comparer les résultats réels aux résultats développés (autre, plus simple, plus évident, moins performant). Habituellement, les tests unitaires comparent les résultats développés à la main. D'autres fois, les tests unitaires utilisent des algorithmes évidents mais extrêmement lents pour produire quelques résultats clés.

L’intérêt de disposer de données de test n’est pas qu’il s’agisse d’un "gelé". résultat. L'intérêt d'avoir des données de test est qu'il s'agit d'un résultat indépendant. Fait différemment - parfois par différentes personnes - ce qui confirme le bon fonctionnement de la fonction.

Désolé. Cela me semble être une mauvaise idée. on dirait que cela subvertit l'intention du test unitaire.

"TOUJOURS, personne ne peut nier les frais généraux liés à l'écriture de scénarios de test"

En fait, beaucoup de gens nieraient le "frais généraux". Ce n'est pas "overhead" dans le sens de perdre du temps et des efforts. Pour certains d'entre nous, il est essentiel de rester immobile. Sans eux, le code peut fonctionner, mais seulement par accident. Avec eux, nous avons amplement la preuve que cela fonctionne réellement; et les cas spécifiques pour lesquels cela fonctionne.

Autres conseils

Souhaitez-vous mettre en œuvre des invariants ou des conditions de post?

Vous devez spécifier explicitement le résultat, cela résoudra la plupart de vos problèmes.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top