Exec Statement Question

Dave Angel davea at ieee.org
Mon Nov 30 12:12:49 EST 2009


Victor Subervi wrote:
> On Sun, Nov 29, 2009 at 10:23 PM, Dave Angel <davea at ieee.org> wrote:
>
>   
>> exec is a statement, and statements don't have "return values."   It's not
>> a function, so there are no parentheses in its syntax, either.  exec is also
>> a technique of last resort;  there's nearly always a better/safer/faster way
>> to accomplish what you might want, but of course you don't say what that is.
>>
>> As for "returning" values, exec by default uses the same global space as
>> your app, so you can just modify a global variable in your "called" code and
>> use it afterwards.
>>
>> abc = 42
>> value = 12
>> exec "abc = %d" % value
>> print abc
>>
>>     
>
> Taking out the parenthesis did it! Thanks. Now, you state this is an option
> of last resort. Although this does indeed achieve my desired aim, here is a
> complete example of what I am trying to achieve. The following is from
> 'createTables2.py':
>
>   for table in tables:
>     try:
>       exec 'from options import %s' % table
>     except:
>       pass
>     try:
>       exec '%s()' % table
>     except:
>       pass
>
>
> The following is from 'options.py':
>
> def jewelry(which=''):
>   code = []
>   names = []
>   meanings = []
>   code.append(['5', '5&frac12;', '6', '6&frac12;', '7', '7&frac12;', '8',
> '8&frac12;', '9', '9&frac12;', '10', '10&frac12;', '11', '11&frac12;', '12',
> '12&frac12;', '13', '13&frac12;'])
>   meanings.append('The standard ring sizes.')
>   names.append('ringSizes')
>   code.append(['Petite (7")', 'Average (7&frac12;")', 'Large
> (8")', 'Extra-large (8&frac12;")'])
>   meanings.append('The standard bracelet sizes.')
>   names.append('braceletSizes')
>   code.append(['16"', '18"', '20"', '22"', '24"'])
>   meanings.append('The standard necklace sizes.')
>   names.append('necklaceSizes')
>   code.append(['14K gold', '18K gold', 'silver', '14K white gold', '18K
> white gold', 'platinum', 'tungsten', 'titanium'])
>   meanings.append('The standard jewelry metals.')
>   names.append('metals')
>   code.append(['diamond', 'emerald', 'ruby', 'sapphire', 'pearl', 'opal',
> 'topaz', 'onyx', 'lapiz lazuli', 'tanzanite', 'garnet', 'quartz', 'rose
> quartz', 'amethyst', 'alexandrite', 'peridot', 'tourmaline', 'citrine',
> 'turquoise'])
>   meanings.append('The standard jewelry stones.')
>   names.append('stones')
>   if which == '':
>     i = 0
>     all = ''
>     while i < len(meanings):
>       table = '%s\n' % meanings[i]
>       table += "<table>\n <tr>\n  <td colspan='8' align='center'>%s</td>\n
> </tr>" % names[i]
>       j = 0
>       for elt in code:
>         if (j + 8) % 8 == 0:
>           table += ' <tr>\n'
>         table += '  <td>%s</td>\n' % code[i]
>         if (j + 8) % 8 == 0:
>           table += ' </tr>\n'
>         j += 1
>       if table[-6:] != '</tr>\n':
>         table += ' </tr>\n'
>       table += '</table>\n'
>       all += table + '<br /><br />'
>       i += 1
>     print all
>
> This all works fine; however, if there is a better way of doing it, please
>   

> let me know.
> Thanks,
> V
>
>   
The parentheses can't do any harm for that particular expression, so 
that wasn't your problem.  But I'm glad you fixed whatever else was your 
problem.  I mentioned it because sometimes they can cause problems, and 
you shouldn't get in bad habits.  (things like if, return, and exec are 
all statements that take an expression, but do not need parentheses.)

I'll throw in a comment here about a bare except.  Also a bad idea.  You 
could easily mask some other problem involved in the import, and the 
program silently continues.  If you know of a specific problem, or 
category of problems that you want to ignore, then pick an exception, or 
tuple of exceptions, to do that.


The immediate question is how to get rid of exec.  You're using it two 
places.  First case, you're just using it to extract specific objects 
from that module's namespace.

Assuming you've already imported options earlier in the code, you can 
replace
    from options import xyzzy
by
    optfunc = options.xyzzy
or
    option_func = getattr(options, "xyzzy", None)
or even
    option_func = getattr(options, "xyzzy", dummyfunc)
(where dummyfunc() is a function which takes no arguments and does nothing)

Now, assuming you plan to call each such function immediately, you can 
just say
    option_func()
in the same loop.


def  dummyfunc():
     pass

....
    for table in tables:
         option_func = getattr(options, table, dummyfunc)
         option_func()

If you didn't have the dummyfunc, you'd need an "if option_func" in there.


Naturally, if this "tables" comes from the user, you need to give him 
some feedback, so perhaps dummyfunc isn't an empty function after all, 
but is supplied in options.py

Other comments about your code:  Someone else has mentioned names, so I 
won't dwell on that.

      if table[-6:] != '</tr>\n':

should use  endswith(), and you won't run the risk of counting wrong if 
your literal changes.

        if (j + 8) % 8 == 0:

could be simpler:

        if j % 8 == 0:

The pattern:
            j=0

      for elt in code:
should be replaced by:
      for j, elt in enumerate(code):

(and of course don't forget to then remove the j+=1 )

You're using the indexing variable i when it'd make much more sense to 
use zip.  Or perhaps meanings and code should be a single list, with 
each item being a tuple.  That's what zip builds, after the fact.  But 
if you build it explicitly, you're less likely to accidentally have an 
extra or missing element in one of them, and have to deal with that bug.

The zip approach might look something like:
    for meaning, code_element in zip(meanings, code):

and then whenever you're using meanings[i]  you use meaning, and 
whenever you're using code[i], you use code_element.  (Obviously name 
changes would help, since code is a list, but the name isn't plural)



More generally, when I see code with that much data embedded in it, it 
cries out for templates.  They're known by many different names, but the 
idea is to write your data structures somewhere else, and manipulate 
them with the code, instead of embedding it all together.  I suspect 
that each of these functions looks very similar, and they each have 
their own set of bugs and glitches.  That's a good indicator that you 
need to separate the data.

For my web work, I've written my own, very simple templating logic 
that's only as good as I needed.  So I can't comment on the various ones 
that are already available.  But the idea is you put data into one or 
more text files, which you systematically manipulate to produce your end 
result.  A particular text file might be comma delimited, or defined in 
sections like an .ini file, or both, or some other mechanism, such as 
xml.  But make it easy to parse, so you can concentrate on getting the 
data into its final form, consistently, and with common code for all 
your tables, parameterized by the stuff in the data files.

DaveA




More information about the Python-list mailing list