Welcome, guest | Sign In | My Account | Store | Cart

This class encapsulates a string with embedded variable names. They are usually evaluated when the object's __str__() method is called. You can specify that they be evaluated when the object is created by including the immediate argument to the constructor. The doc string at the top of the module has an example of its use.

Itamar Shtull-Trauring's printexpr.py inspired me to try this out.

Python, 73 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
"""VarString.py

Define the VarString class that encapsulates a string with embedded
variables.  For example:

from VarString import VarString

fromAddr = '"Samuel Clemens" <sam@marktwain.com>'
msg = VarString('''From: $fromAddr
To: $toAddr
Subject: A short message

Dear $toName,

Just a quick message to let you know that reports of my death
are greatly exaggerated.

Regards,
Samuel Clemens.
''')
# infoSource() returns a list of 2-tuples.
for toAddr, toName in infoSource():
    sendMsg(str(msg))

"""

import types, re, sys

class VarString:
    """A string with embedded variables signified by $a $b etc."""
    def __init__(self, string, immediate=None):
        if type(string) != types.StringType:
            raise TypeError
        self.__immediate = immediate
        if immediate:
            self.__str = self.__process(string)
        else:
            self.__str = string

    def __str__(self):
        if self.__immediate:
            return self.__str
        else:
            return self.__process(self.__str)

    def __process(self, str):
        r = re.compile("\$([A-Za-z_][A-Za-z0-9_]*)")
        self.__caller_globals, self.__caller_locals = _caller_symbols()
        newstr = r.sub(self.__embsub, str)
        return newstr
        

    def __embsub(self, match):
        name = match.group(1)
        if self.__caller_locals.has_key(name):
            return str(self.__caller_locals[name])
        elif self.__caller_globals.has_key(name):
            return str(self.__caller_globals[name])
        else:
            raise NameError, "There is no variable named '%s'" % (name)

def _caller_symbols():
    """Global and local symbols from three calling frames back

    Thanks to Itamar Shtull-Trauring for showing how this is done."""
    try:
        raise StandardError
    except StandardError:
        fr = sys.exc_info()[2].tb_frame
        # We go back three calling frames: to __process() to
        # __init__() or __str__() to its caller.
        return (fr.f_back.f_back.f_back.f_globals,
                fr.f_back.f_back.f_back.f_locals)

There are two improvements I would like to make:

  • Allow \$ to be used to embed dollar signs in the string. My understanding of regexps is not currently up to that, but I guess it is simple enough. (Actually I was a bit confused by only needing one backslash in the string that defines the regexp.)

  • When raising the NameError exception, include a traceback object to show the error as occurring in the frame that calls the VarString object's method. I couldn't figure out how to do that.

Should I catch an exception if str() fails on the object specified by the variable?

1 comment

Radek Kanovsky 19 years, 6 months ago  # | flag

% string operator. I think that % string operator enables you to do almost the same more efficiently.

template = "ttt %(x)s ttt"
for x in range(3) :
    print template % locals()

result:
ttt 0 ttt
ttt 1 ttt
ttt 2 ttt
Created by Michael Strasser on Thu, 22 Mar 2001 (PSF)
Python recipes (4591)
Michael Strasser's recipes (1)

Required Modules

Other Information and Tasks