Problem with Lexical Scope

Bengt Richter bokr at oz.net
Fri Dec 16 12:52:14 CET 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