Простой декоратор поведения замораживания - PullRequest
2 голосов
/ 17 марта 2009

Я пытаюсь написать декоратор заморозки для Python.

Идея заключается в следующем:

(в ответ на два комментария)

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

  • Одним из них является разработка, основанная на тестировании: В идеале разработчики пишут кейс перед написанием кода. Обычно это помогает определить архитектуру, потому что эта дисциплина заставляет определять реальные интерфейсы до разработки. Можно даже считать, что в некоторых случаях человек, который отправляет задание между dev пишет тестовый пример и используйте его, чтобы эффективно проиллюстрировать спецификацию, которую он имеет в виду. У меня нет опыта использования такого теста.

  • Вторым является идея, что все проекты с приличным Размер и несколько программистов страдает от неработающего кода. Что-то, что используется для работы, может сломаться из-за изменений это было похоже на невинный рефакторинг. Хотя хорошая архитектура, слабая связь между компонентами может помочь бороться с этим явлением; ты будешь спать лучше ночью, если вы написали несколько тестов, чтобы убедиться, что ничто не нарушит поведение вашей программы.

ОДНАКО, Никто не может отрицать накладные расходы на написание тестовых случаев. в В первом случае можно утверждать, что контрольный пример на самом деле руководит развитие и, следовательно, не должны рассматриваться как накладные расходы.

Честно говоря, я довольно молодой программист, и если бы я был Вы, мое слово на эту тему не очень ценно ... Во всяком случае, я думаю, что большинство компаний / проектов не работают и что юнит-тесты в основном используются во втором случай ... * * 1023

Другими словами, вместо того, чтобы гарантировать, что программа работает правильно, он стремится проверить, что он будет работать так же в будущем.

Эту потребность можно удовлетворить без затрат на написание тестов, с помощью этого морозильного декоратора.

Допустим, у вас есть функция

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

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

Что-то такое, что при первом запуске декоратора декоратор запускает функцию с заданными аргументами (ниже 0 и 7) и сохраняет результат на карте (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)

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

Я уже написал быстро декоратор (см. Ниже), но обидел несколько проблем о который мне нужен твой совет ...

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
  • Сброс и сравнение результатов не является тривиальным для всех типов. Сейчас я думаю об использовании функции (я называю это постпроцессом) для решения этой проблемы. По сути, вместо сохранения res я сохраняю postprocess (res) и сравниваю postprocess (res1) == postprocess (res2) вместо сравнения res1 res2. Важно позволить пользователю перегружать предопределенную функцию постобработки. Мой первый вопрос: Вы знаете способ проверить, является ли объект дампируемым или нет?

  • Определение ключа для оформленной функции - это боль. В следующих фрагментах Я использую функциональный модуль и его имя. ** Можете ли вы придумать более разумный способ сделать это. **

  • Фрагменты, приведенные ниже, работают, но открывают и закрывают файл во время тестирования и записи. Это просто глупый прототип ... но знаете ли вы хороший способ открыть файл, обработать декоратор для всех функций, закрыть файл ...

  • Я намерен добавить некоторые функции к этому. Например, добавьте возможность определить итерация для просмотра набора аргументов, записи аргументов из реального использования и т. д. Почему вы ожидаете от такого декоратора?

  • В общем, вы бы использовали эту функцию, зная ее ограничение ... Особенно, когда пытаетесь использовать ее с POO?

Ответы [ 2 ]

3 голосов
/ 17 марта 2009

"В общем, вы бы использовали такую ​​функцию, зная ее ограничение ...?"

Честно говоря - никогда.

Нет обстоятельств, при которых я бы "замораживал" результаты функции таким образом.

Вариант использования, как представляется, основан на двух неверных идеях: (1) что модульное тестирование является либо сложным, либо сложным, либо дорогим; и (2) было бы проще написать код, «заморозить» результаты и каким-то образом использовать замороженные результаты для рефакторинга. Это не полезно Действительно, очень реальная возможность заморозить неправильные ответы делает это плохой идеей.

Во-первых, «согласованность и правильность». Это проще сохранить с помощью простого отображения, чем со сложным набором декораторов.

Сделайте это вместо того, чтобы написать декоратор замораживания.

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

Созданный объект словаря будет отлично работать как замороженный набор результатов. Нет декоратора. Не сложно говорить о.

Во-вторых, на «модульном тестировании».

Точка модульного теста - , а не , чтобы «заморозить» некоторые случайные результаты. Цель модульного теста - сравнить реальные результаты с результатами, разработанными другим (более простой, более очевидный, неэффективный способ). Обычно модульные тесты сравнивают полученные вручную результаты. В других случаях модульные тесты используют очевидные, но ужасно медленные алгоритмы для получения нескольких ключевых результатов.

Смысл наличия тестовых данных не в том, что это «замороженный» результат. Смысл наличия тестовых данных в том, что это независимый результат. Сделано по-разному - иногда разными людьми - это подтверждает, что функция работает.

Извините. Это кажется мне плохой идеей; похоже, это подрывает цель модульного тестирования.


«ОДНАКО, никто не может отрицать накладные расходы на написание тестовых случаев»

На самом деле, многие люди отрицают «накладные расходы». Это не «накладные расходы» в смысле потерянного времени и усилий. Для некоторых из нас юнит-тесты необходимы. Без них код может сработать, но только случайно. С ними у нас есть достаточно доказательств того, что это действительно работает; и конкретные случаи, для которых это работает.

0 голосов
/ 17 марта 2009

Вы хотите внедрить инварианты или условия публикации?

Вы должны явно указать результат, это устранит большинство из вас проблем.

...