4

Ok, so I'm working in an environment where a config script for a tool is an exec'd python script. The exec call is something like this:

outer.py:

exec(open("inner.py").read(), globals(), {})

Now, I want to do some relatively basic iteration within the exec'd script. In this case, doing work when some values aren't in the whitelist:

inner.py:

items = (
  'foo/bar',
  'foo/baz',
  'foof',
  'barf/fizz',
)

whitelist = (
  'foo/',
)

for key in items:
  try:
    # Not terribly efficient, but who cares; computers are fast.
    next(True for prefix in whitelist if key.startswith(prefix))

  # Do some work here when the item doesn't match the whitelist.
  except StopIteration:
    print("%10s isn't in the whitelist!" % key)

Running python inner.py yields the expected result:

      foof isn't in the whitelist!
 barf/fizz isn't in the whitelist!

Here's the strange part: Running python outer.py seems to show that the interpreter is confused about the scope of the generator:

Traceback (most recent call last):
  File "outer.py", line 1, in <module>
    exec(open("inner.py").read(), globals(), {})
  File "<string>", line 15, in <module>
  File "<string>", line 15, in <genexpr>
NameError: global name 'key' is not defined

Some other notes:

  • You can print(key) just fine inside the for loop (before the generator is run).

  • Replacing the empty dict with locals() in the exec line of outer.py resolves the issue, but that code is out of my control.

  • I'm running the OS X built Python 2.7.2 (Mountain Lion).

Nevir
  • 7,951
  • 4
  • 41
  • 50
  • 1
    Interesting also: If you run it with `locals()` instead of `{}` once, and then replace it with `{}` again, it won't print anything. Why are you doing the weird `next()` thing when you could just add another `for` loop, though? – Xymostech Apr 07 '13 at 19:02
  • the `next` call is to just break out once I find a value in the whitelist. IMO the extra `for` loop is a bit more awkward due to requiring a temp `found` var. (In my actual code I'm trying to test for items that _don't_ match) – Nevir Apr 07 '13 at 19:07
  • I too am curious. It seems like a really strange bug. I just was a bit more confused about what your `next` call was doing (I think that relying on something throwing exceptions is bad form, but maybe that's just me). – Xymostech Apr 07 '13 at 19:11
  • Heh, yeah - it'd be nice if there was a simple `find_first` helper in `itertools` or whatever; but `next` appears to be the most straightfoward way :( – Nevir Apr 07 '13 at 19:12
  • 3
    The whole `try: next( .. )` block is just a weird way to write `if any(key.startswith(prefix) for prefix in whitelist)` .. or avoiding `break` at all cost. – Jochen Ritzel Apr 07 '13 at 19:15
  • `any`! That's what I've been looking for all this time > – Nevir Apr 07 '13 at 19:22

2 Answers2

5

Dodgy indeed.

From the doc:

If two separate objects are given as globals and locals, the code will be executed as if it were embedded in a class definition.

(Note that when you run exec(..., globals(), locals()) at module level, this does not apply, because globals() is locals(), i.e. not two separate objects).

This indicates you can simply reproduce the problem by running this script:

class A(object):

  items = (
    'foo/bar',
    'foo/baz',
    'foof',
    'barf/fizz',
  )

  whitelist = (
    'foo/',
  )

  for key in items:
    try:
      # Not terribly efficient, but who cares; computers are fast.
      next(True for prefix in whitelist if key.startswith(prefix))
      # Found!
      print(key)
    except StopIteration:
      pass

"Ok, but why do I get the error here?"

So glad you asked. The answer is here.

Community
  • 1
  • 1
shx2
  • 61,779
  • 13
  • 130
  • 153
  • 1
    I also missed it the first time reading. The human mind tends to focus on the things it thinks it knows, sometimes ignoring the things which don't make sense. Glad I could help ;) – shx2 Apr 07 '13 at 19:23
1

Why don't you just add an inner for loop there:

for key in items:
    for prefix in whitelist:
        if key.startswith(prefix):
            print(key)

I'm pretty sure you won't get that error and it's much simple/easier to read.

gonz
  • 5,226
  • 5
  • 39
  • 54
  • Yeah, I agree that for my example, the nested `for` loops is far better to read! However, in my actual use case, I'm trying to do work when things _don't_ match (and the doubly-nested `for` loop breaks down a bit with that) – Nevir Apr 07 '13 at 19:15