EvalDict: takes 2 and 3 ... (long) [was: string interpolation, PEP 216, and other assorted threads]

2 views
Skip to first unread message

Steven Majewski

unread,
Jan 12, 2002, 6:22:02 PM1/12/02
to

OK. To cut to the chase and first show a demo of string interpolation
using an evaluating dictionary (EvalDict):


## first define some variables:

>>> from math import *
>>> x = pi/3
1.0471975511965976
>>> formula = 'sqrt( sin(x)**2 + cos(x)**2 )'
'sqrt( sin(x)**2 + cos(x)**2 )'

## then make an EvalDict that captures this modules namespace:

>>> ED = EvalDict(globals())


>>> print '\n %(formula)s = %(%formula)s for x = %(x)s' % ED

sqrt( sin(x)**2 + cos(x)**2 ) = 1.0 for x = 1.0471975512


Note the two level replacement for '%(%formula)s' vs. '%(formula)s'.


---- evolution ----

Back in an earlier thread about string interpolation, I suggested
a class based solution was more 'pythonic' than adding new syntax.
I posted an initial suggestion -- something like:

class EvalDict1(dict):
def __init__( self, d ):
self.d = d
def __getitem__( self, key ):
try:
return self.d[key]
except KeyError:
return eval(key, self.d)

I wasn't quite sure off the top of my head, how to use the new
class/types properly. In that version above, an EvalDict1 instance
prints as '{}', because the lookup is done in self.d. It also has
the benefit(?) that, since a reference to another dict is kept,
rather than a copy,if you create an instance with 'EvalDict1(globals())',
and then add new variables to the namespace, they will be found in the
reference to the original dict.

(? "benefit" : this might be easier to use interactively, but it
might make some confusing side effects. You can always call
update() method for the EvalDict. Explicit may be better here. )


Version 2 is more in the proper style for new class/types, and it
copies values into the new dict, rather than keeping a reference.
(Also allows multiple dicts to be passed on initialization.)

class EvalDict2(dict):
def __init__(self, *ds ):
dict.__init__(self)
for d in ds:
self.update( d )
def __getitem__( self, key ):
try:
return dict.__getitem__( self, key )
except KeyError:
return eval( key, self )


# The problem with this version is that a double evaluation has
# an awkward format ( '%%(%(formula)s)s' ) and it requires and
# explicit double application ( formatstring % E % E ):

E = EvalDict2(globals())

tststr = '%(formula)s, %%(formula)s, %%(%(formula)s)s'
print '\ntest-string:'
print tststr
print '\none subs:'
print tststr % E
print '\ntwo subs:'
print tststr % E % E

## produces the output:

test-string:
%(formula)s, %%(formula)s, %%(%(formula)s)s

one subs:
sqrt( sin(x)**2 + cos(x)**2 ), %(formula)s, %(sqrt( sin(x)**2 + cos(x)**2 ))s

two subs:
sqrt( sin(x)**2 + cos(x)**2 ), sqrt( sin(x)**2 + cos(x)**2 ), 1.0


## A perhaps better version allows a less complicated syntax for
## a double substitution ( '%(%formula)s' ) and does it in a
## single application:


class EvalDict3(dict):
def __init__(self, *ds ):
dict.__init__(self)
for d in ds:
self.update( d )
def __getitem__( self, key ):
try:
return dict.__getitem__( self, key )
except KeyError:
if key[0] == '%': ## here's the added special case
key = dict.__getitem__(self, key[1:] )
return eval( key, self )

print '\n\nEvalDict3:',repr('%(formula)s = %(%formula)s for x = %(x)s')
print '%(formula)s = %(%formula)s for x = %(x)s' % EvalDict3(globals())

## Produces the output:

EvalDict3: '%(formula)s = %(%formula)s for x = %(x)s'
sqrt( sin(x)**2 + cos(x)**2 ) = 1.0 for x = 1.0471975512

---- more discussion ----

If you want to use something other than '%' as the format char,
you need to define a new str type that implements a new __mod__ method.
'%' is still the operator, even if the rules for format strings are
different.

Or, you may be able to define __rmod__ for the dict type.
( Not sure if you need to do anything special to __coerce__ )

PEP 216 uses '$' for both a prefix operator ( or is it a prefix
macro ? -- I'm not sure.) Rather than adding new special syntax,
I'ld rather see extra chars mapped to a whole set of new class
operator methods: '!', '@', '$', could all be mapped to new
__operator__ names. This might end up making Python look more like
Perl, but I'ld rather the more general solution than to be chipped
away bit by bit with new syntax PEPs.

Enhancement:
Let __init__ allow modules as args as well as dicts. ( or possibly,
anything with a __dict__ attribute. )

Limits:
Only does two level substitution.
I don't think this use requires any more, and more might be too
confusing. However, recursive application might be useful for
a sort of template expansion language.

-- Steve Majewski

Reply all
Reply to author
Forward
0 new messages