Chuk Ultima
Chuk Ultima

Reputation: 1037

Inner function not accessible to dict comprehension

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

Answers (1)

jferard
jferard

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

Related Questions