Reputation: 1037
I'm writing some Python 2.7.12 code and am quite surprised about the following error in this code execution :
def validate_available_links(self, link_dict, sub_element=None):
def strip_dirt(key_val):
#TODO: properly strip HTML encoded characters
key_val = key_val.replace("\u2039", "<")
key_val = key_val.replace("\u203a", ">")
return key_val
# Scrape all the links from the current webpage and verify
# it with the provided link dictionary.
if sub_element is None:
act_links = self.suiteContext['browser'].webdriver.find_elements_by_tag_name('a')
else:
act_links = sub_element.find_elements_by_tag_name('a')
result = {strip_dirt(lnk.text): lnk.get_attribute('href') for lnk in act_links}
diff_keys = set(result.keys()) - set(link_dict.keys())
diff_values = set(result.values()) - set(link_dict.values())
self.tear_down_hook()
for l_text, l_url in link_dict.iteritems():
self.cfg.logger.info("[VALIDATION] Verify Link text [{}] and URL [{}]."
.format(l_text, l_url))
import pdb; pdb.set_trace()
And when executing the code
(Pdb) result = {strip_dirt(lnk.text): lnk.get_attribute('href') for lnk in act_links}
*** NameError: global name 'strip_dirt' is not defined
(Pdb) strip_dirt
<function strip_dirt at 0x0651BBB0>
(Pdb) result = {strip_dirt(lnk.text): lnk.get_attribute('href') for lnk in act_links}
*** NameError: global name 'strip_dirt' is not defined
(Pdb) strip_dirt('x')
'x'
(Pdb) {strip_dirt(lnk.text): lnk.get_attribute('href') for lnk in act_links}
*** NameError: global name 'strip_dirt' is not defined
Can anyone explain why the inner function strip_dirt
is not accessible to the dictionary comprehension but is for the rest of the outer function?
Upvotes: 1
Views: 47
Reputation: 8180
I'm not a pdb
expert, so please correct me if I'm wrong.
Here's a MCVE:
import pdb
def f():
def g(n): return 2*n
pdb.set_trace()
f()
Now, in pdb
, as expected:
ipdb> g(5)
10
But where does the g
name come from?
ipdb> 'g' in globals()
False
ipdb> 'g' in locals()
True
Ok, g
is in the locals()
variables of f
. When you create a list or dict comprehension, you have new locals()
variables:
ipdb> [locals() for _ in range(1)]
[{'_': 0, '.0': <range_iterator object at 0x7f1924003d80>}]
Hence, in a list/dict comprehension, g
is neither in locals()
nor in globals()
:
ipdb> [g(1) for _ in range(1)]
*** NameError: name 'g' is not defined
Now, the big question: why does this work in the running program and not in ipdb
? I hope I have the beginning of an explanation:
import pdb
import inspect
def f():
def g(n): return 2*n
print([g(1) for _ in range(1)])
print([inspect.stack()[1].frame.f_locals for _ in range(1)])
pdb.set_trace()
f()
# output:
[2] # it works because...
[{'g': <function f.<locals>.g at 0x7f1916692488>}] # ...g is in the parent stack frame
In ipdb
, as noticed:
ipdb> [g(1) for _ in range(1)]
*** NameError: name 'g' is not defined
But if you take g
manually from the parent frame, it works:
ipdb> [inspect.stack()[1].frame.f_locals for _ in range(1)]
[{'g': <function f.<locals>.g at 0x7f1916692488>, '__return__': None}]
ipdb> [inspect.stack()[1].frame.f_locals['g'](1) for _ in range(1)]
[2]
Conclusion: it seems that ipdb
does not give direct access to the values stored in the parent frame, unlike a running program.
Please note that this is problably largely depends on the Python implementation. (I used CPython.)
Upvotes: 1