11

The code is from the guide of pyquery

from pyquery import PyQuery
d = PyQuery('<p class="hello">Hi</p><p>Bye</p>')
d('p').filter(lambda i: PyQuery(this).text() == 'Hi')

My question is this in the 3rd line is an unbound variable and is never defined in current environment, but the above code still works.

How can it work? Why it doesn't complain NameError: name 'this' is not defined?

It seems that something happens at https://bitbucket.org/olauzanne/pyquery/src/c148e4445f49/pyquery/pyquery.py#cl-478 , could anybody explain it?

phant0m
  • 15,502
  • 4
  • 40
  • 77
Hanfei Sun
  • 39,245
  • 33
  • 107
  • 208

3 Answers3

4

This is done via Python's func_globals magic, which is

A reference to the dictionary that holds the function’s global variables — the global namespace of the module in which the function was defined.

If you dive into PyQuery code:

def func_globals(f):
    return f.__globals__ if PY3k else f.func_globals

def filter(self, selector):
    if not hasattr(selector, '__call__'):
        return self._filter_only(selector, self)
    else:
        elements = []
        try:
            for i, this in enumerate(self):

                # The magic happens here
                func_globals(selector)['this'] = this

                if callback(selector, i):
                    elements.append(this)

        finally:
            f_globals = func_globals(selector)
            if 'this' in f_globals:
                del f_globals['this']
        return self.__class__(elements, **dict(parent=self))
Zaur Nasibov
  • 20,924
  • 9
  • 49
  • 79
  • Thanks, BasicWolf. But what's the meaning of `for i, this in enumerate(self)`, why would that be a loop to set the value of `func_globals(selector)['this']`? – Hanfei Sun Aug 05 '12 at 15:26
  • Well, enumerate creates a sequence of [(index, element)] out of an iterable (and PyQuery class is iterable as a subclass of `list`). So, `i` is the index of an element and `this` refers to an element stored in the PyQuery object. The best way to understand how PyQuery works in-depth, is to dive into its code. – Zaur Nasibov Aug 05 '12 at 16:40
1

Others have correctly point out how this is defined inside that lambda you are talking about.

To elaborate a bit more, try out the following code:

>>> def f():
...     print f_global
... 
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
NameError: global name 'f_global' is not defined
>>> f.__globals__['f_global'] = "whoa!!" #Modify f()'s globals.
>>> f()
whoa!!

This is exactly what is happening there. On line 496, you would see the following code:

for i, this in enumerate(self):             #this is the current object/node.
     func_globals(selector)['this'] = this  #func_globals returns selector.__globals__
UltraInstinct
  • 38,991
  • 11
  • 73
  • 100
-2

This doesn't throw a NameError because the variable might exist at the time the actual function is called.

>>> f = lambda i: some_non_named_var
>>> f(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <lambda>
NameError: global name 'some_non_named_var' is not defined

The above does not error until you call the function that you've stashed away. In the example code you showed, they are manually setting a variable called this in the func_globals before calling the lambda selector function.

Josh Smeaton
  • 43,953
  • 24
  • 121
  • 160
  • Why is this being downvoted? It might not be as complete as some of the other answers, but it still demonstrates exactly what is happening.. – Josh Smeaton Sep 14 '12 at 01:44