2 views

Skip to first unread message

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

Search

Clear search

Close search

Google apps

Main menu