Problem with Lexical Scope
Bengt Richter
bokr at oz.net
Fri Dec 16 06:52:14 EST 2005
On 15 Dec 2005 18:45:17 -0800, "jslowery at gmail.com" <jslowery at gmail.com> 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
More information about the Python-list
mailing list