Problem with Lexical Scope

Bengt Richter bokr at
Fri Dec 16 12:52:14 CET 2005

On 15 Dec 2005 18:45:17 -0800, "jslowery at" <jslowery at> 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)
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
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
>box_rule = when(all(have('length'), have('width')), eq('length',
>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(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 ;-)

----< >------------------------------------------
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})
 >>> jslowery.rule({'length':2, 'width':1})
 >>> jslowery.rule({'height':2, 'width':1})
 >>> jslowery.box_rule({'height':2, 'width':1})
 >>> jslowery.box_rule({'length':2, 'width':1})
 >>> jslowery.box_rule({'length':2, 'width':2})

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.

Bengt Richter

More information about the Python-list mailing list