[Tutor] Named-value formatting fails

Steven D'Aprano steve at pearwood.info
Sun Jan 9 23:14:10 CET 2011


Tim Johnson wrote:
> * Steven D'Aprano <steve at pearwood.info> [110108 19:46]:
>> A more detailed response.
>>
>> Dear me... messing with globals and locals. That's always a bad sign.  
>> But let's assume this is that one time in 100 that it is actually  
>> justified...
>>
>>>         if localvals is None:             self.locals = 
>>> sys._getframe(1).f_locals
>> Are you aware that this is non-portable and subject to change without  
>> notice?
>   No! 
>   1)Can you explain further?

The convention in Python is that any name that starts with a single 
leading underscore is a private implementation detail, and therefore 
subject to change. _getframe starts with a single underscore.

Of course, it's been around effectively forever -- it was introduced in 
Python 2.1, and is unlikely to be removed any time soon. But it *could* 
be, and it could happen without notice, so you have to weigh up the 
benefit you get from using an implementation detail against the risk of 
it disappearing.

On the plus side, it is a *documented* internal detail, which suggests 
that it won't disappear without notice.

http://docs.python.org/library/sys.html#sys._getframe

But notice that it is specific to CPython (the version you are using). 
Other Pythons, such as Jython, IronPython, PyPy, Stackless, and many 
others, may not include this function. So code using it is non-portable.

(E.g. IronPython only supports frames if you pass the commandline switch 
-X:Frames or -X:FullFrames.)

(Also note that PyPy is now about twice as fast as CPython. I would 
expect that over the next few years, PyPy should start to take off as 
the preferred Python implementation.)


>   And you have convinced me, and furthermore given me enough to
>   convince someone else that a different approach would be safer.
>   2)Can you recommend a *simple* templatting module?


You haven't actually told us what you're trying to do, so I have to 
guess. That means I may have misunderstood something. But it looks like 
you want to be able to use templating of arbitrary variable names.

Perhaps your Evalx class is overkill. Maybe this is all you need:

 >>> a = 42
 >>> mydict = globals().copy()
 >>> mydict.update(locals())
 >>> "value = %(a)s" % mydict
'value = 42'

You might also like to look at the string.Template class, which is for 
substituting $ templates.

If you want to stick with Evalx, the approach I'd consider is:

* support globals() and locals() if they are explicitly given (*reading* 
is safer than *writing*);

* support automatic lookup of globals and locals only when supported, 
otherwise fail gracefully;

* don't use eval unless you really need to;

* if you think you need to, you probably don't :)


That means that instead of:

if localvals is None:
     self.locals = sys._getframe(1).f_locals

do something like this:

if localvals is None:
     try:
         self.locals = sys._getframe(1).f_locals
     except AttributeError:
         self.locals = {}

(and similarly for globals).


Then get rid of the eval in your __getitem__. You probably don't 
*really* need to support cases like this, no matter how tempting it 
might be:

a = 42
b = 23
"value = %(a+b)s" % Evalx()
=> "value 75"

Instead:

      def __getitem__(self, key):
         if key in self.locals: return self.locals[key]
         elif key in self.globals: return self.globals[key]
         else: return "Key? What key?"
         # Or raise KeyError



-- 
Steven



More information about the Tutor mailing list