我正在尝试为 Python 编写一个冻结装饰器。
思路如下:
(针对两条评论)
我可能错了,但我认为测试用例有两个主要用途。
一种是测试驱动开发:理想情况下,开发人员在编写代码之前先编写案例。它通常有助于定义架构,因为该规则强制在开发之前定义真实的接口。人们甚至可能会认为,在某些情况下,在开发人员之间分配工作的人正在编写测试用例并用它来有效地说明他所考虑的规范。我没有任何使用这种测试用例的经验。
第二个想法是,所有规模不错的项目和几个程序员都遭受代码损坏的困扰。以前可以正常工作的东西可能会因为看起来像无辜重构的变化而被破坏。虽然架构不错,但组件之间的松散耦合可能有助于对抗这种现象;如果您编写了一些测试用例以确保没有任何东西会破坏程序的行为,那么您晚上会睡得更好。
然而,没有人可以否认编写测试用例的开销。在第一种情况下,人们可能会争辩说测试用例实际上是在指导开发,因此不应被视为开销。
坦率地说,我是一个非常年轻的程序员,如果我是你,我在这个主题上的话并不真正有价值......无论如何,我认为大多数公司/项目都不是这样工作的,并且主要使用单元测试在第二种情况下...
换句话说,它的目的不是确保程序正常工作,而是检查它将来是否会正常工作。
通过使用这种冻结装饰器,无需编写测试成本即可满足此需求。
假设你有一个函数
def pow(n,k):
if n == 0: return 1
else: return n * pow(n,k-1)
它非常好,您想将其重写为优化版本。这是一个大项目的一部分。您希望它为几个值返回相同的结果。与其经历测试用例的痛苦,不如使用某种冻结装饰器。
第一次运行装饰器时,装饰器使用定义的 args(低于 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)
下次执行程序时,装饰器将加载此映射并检查此函数对这些 args 的结果是否未更改。
我已经很快写了装饰器(见下文),但伤害了一些我需要你建议的问题......
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 一起使用时?