On 15 Dec 2005 18:45:17 -0800, "[EMAIL PROTECTED]" <[EMAIL PROTECTED]> wrote:
>Well, the the comparison operations are just a special case really.I >don't know about the obfuscation contest. I've attempted to make an >extensible library. Sorry about "obfuscation contest," I just balked at the reduce code, which seemed like premature overgeneralization ;-) > >def lt(*fields): > return collect(fields, lambda x, y: x < y) > >def gt(*fields): > return collect(fields, lambda x, y: x > y) > >def gte(*fields): > """ gte(field, ...) -> rule > """ > return collect(fields, lambda x, y: x >= y) > >etc... DRY ? ;-) > >The purpose of writing the collect function was just to be able to >easily apply the logic to whatever function was wanted. > >the added ability is to be able to take as many arguments as it wants. > Yes, but does collect(fields, fun) always know that fun takes two args? And that it makes sense to apply fun pairwise as a boolean relation to reduce to a single boolean value? You could say that by definition it does. In that case, will your future library extender need to add some other kind of collect-like function? >hire_rule = lt('birth_date', 'hire_date', 'fire_date') >cube_rule = eq('height', 'width', 'depth') I see where the idea of using reduce would occur ;-) > >The reason that it's written like this is because I'm attempting to >make things as declarative as possible. I could use classes and >override __special__ operators and things like that but just applying >rules using prefix notation seems to work alright. Applying or generating? But yeah, prefix should work fine. Or infix. > >I've basically got a simple call signature...takes dict returns bool >that plugs into the little validation routine. Simple is good. > >It turns out that envoking the rules is easistly expressed with a >closure, though I might switch to types if it needs that added >complexity. It's good that you have a syntax that can be implemented either way. > >But your assumption is correct, it's for dictionaries of data to be >validated. I haven't decided if I want to take advantage of the dict's >mutability to include formating as well. You might consider putting formatting in dict subclasses that have appropriate __str__ and/or __repr__ methods, and doing print Fmtxxx(record_dict) or such? > >Here's a bit from my unit test. Looks nice. And the syntax is regular enough that you can probably write an optimizing rule generator when/if you need it. > >rule = when(all(have('length'), have('width')), > check(['length', 'width'], lambda x, y: x == y)) >assert rule({'length' : '2', 'width' : '2'}) == True >assert rule({'length' : '2', 'width' : '1'}) == False >assert rule({'length' : '1', 'width' : '2'}) == False > But what about when the "when" clause says the rule does not apply? Maybe return NotImplemented, (which passes as True in an if test) e.g., assert rule({'length' : '1', 'height' : '2'}) is NotImplemented >I've also got a "collectable" equality function so that can be shown >as. > >box_rule = when(all(have('length'), have('width')), eq('length', >'width')) > >Which basically means, if we have both a length and a width, then be >sure they are equal. either way, I think it would be better to give tests names, i.e., instead of the lambda, pass a function def eq(x,y): return x==y and then also change check to have signature def check(testfun, *fields):... But "collectable" has the possibility of inline code generation (see below ;-) > >Of course, there is a little function that performs a conjunction of a >complete list of rules on a dict and returns the rules that failed. > >I've also got a little adapter that translates functions that take a >string and returns bool into one that fits the call signature called >match. > >match(is_ssn, 'social-security_number') > >Like I said, it may be considered more readable if using operator >overloading so that it uses python's native syntax. Considering the >added complexity, I don't know if it would be worth it. I'd probably >put a simple little declarative language on top of it to translate the >function calls before that. > Note that you could treat your when, all, have, and check as code source generators and compile a rule as the last part of "when" processing, e.g., (fresh off the griddle, only tested as far as you see ;-) ----< jslowery.py >------------------------------------------ def all(*tests): return '(' + ' and '.join(tests) +')' def have(*fields): return '(' + ' and '.join('"%s" in record'%field for field in fields) + ')' def check(tfun, *fields): return '%s(%s)' % (tfun.func_name, ', '.join('record[%r]'%field for field in fields)) def when(cond, test): g = globals() d = dict((name, g[name]) for name in __testfuns__) src = """\ def rule(record): if not (%s): return NotImplemented return (%s) """ %(cond, test) print src # XXX debug exec src in d return d['rule'] def eq(*fields): # "collectable" return '(' + ' == '.join('record[%r]'%(field,) for field in fields) + ')' def eq_test(x, y): # not "collectable" in itself, use with check return x==y __testfuns__ = ['eq_test'] #rule = when(all(have('length'), have('width')), # check(['length', 'width'], lambda x, y: x == y)) # rewritten with changed check, differentiating eq_test from "collectable" eq rule = when(all(have('length'), have('width')), check(eq_test, 'length', 'width')) box_rule = when(all(have('length'), have('width')), eq('length', 'width')) ------------------------------------------------------------- Then: (I left the debug print in, which prints the rule source code, with name "rule" all the time. You could always change that) >>> import jslowery def rule(record): if not ((("length" in record) and ("width" in record))): return NotImplemented return (eq_test(record['length'], record['width'])) def rule(record): if not ((("length" in record) and ("width" in record))): return NotImplemented return ((record['length'] == record['width'])) So the "collectable" eq for box_rule (second debug output) generated in-line code, which will run faster. >>> jslowery.rule({'length':2, 'width':2}) True >>> jslowery.rule({'length':2, 'width':1}) False >>> jslowery.rule({'height':2, 'width':1}) NotImplemented >>> jslowery.box_rule({'height':2, 'width':1}) NotImplemented >>> jslowery.box_rule({'length':2, 'width':1}) False >>> jslowery.box_rule({'length':2, 'width':2}) True Looks like I didn't need all the parens ;-) Anyway, this is not well tested, but rules constructed this way should run a lot faster than doing all the nested calling at run time. There are always security issues in exec-ing or eval-ing, so I am not recommending the above as secure from malicious rule-writers, obviously. Regards, Bengt Richter -- http://mail.python.org/mailman/listinfo/python-list