I have a module named Promotions with some functions, and I have a variable that stores a list of all the functions containing "_promo" in the name.
promos = [
locals()[name] for name in locals() # Iterate over each name in the dictionary returned by locals()
if name.endswith("_promo") # Select only names that end with the _promo suffix.
]
When I import Promotions in another place and ask to retrieve promos, I get a KeyError
.
However, if I do the same thing but replace locals()
with globals()
, I don't get a KeyError
.
Does anyone know why?
edit:
Is it because the second time I call locals()
(in locals()[name]
) I am no longer in the same scope?
Is it because the second time I call locals() (in locals()[name]) I am no longer in the same scope?
That's exactly the case. List comprehension have it's own scope just like a function, but the iterator over locals()
is created in the outer scope.
import inspect
class LoudIterable:
def __iter__(self):
print(inspect.currentframe())
return iter([1, 2])
x = [print(inspect.currentframe()) for i in LoudIterable()]
# <frame at 0x0000021795BFD4B8, file '', line 5, code __iter__>
# <frame at 0x0000021795CF8AF8, file '', line 8, code <listcomp>>
# <frame at 0x0000021795CF8AF8, file '', line 8, code <listcomp>>
You'll see that the each iteration has the same frame, but the __iter__
was called in an another frame.
And that makes sense when you think about generators.
non_iterable = 2
x = (i for i in non_iterable)
The iter
is called on the iterable in an eager way, even though we haven't even started the iteration, you'll see the error straight away:
TypeError: 'int' object is not iterable
Anyway, the easy fix is this:
promos = [v for k, v in locals().items() if k.endswith("_promo")]