Почему эта несвязанная переменная работает в Python (pyquery)?

Код из руководства pyquery

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

Мой вопрос this в третьей строке является несвязанной переменной и никогда не определяется в текущей среде, но приведенный выше код все еще работает.

Как это работает? Почему он не жалуется NameError: name 'this' is not defined?

Кажется, что-то происходит в https://bitbucket.org/olauzanne/pyquery/src/c148e4445f49/pyquery/pyquery.py#cl-478, может ли кто-нибудь объяснить это?

Ответ 1

Это делается с помощью магии Python func_globals, которая

Ссылка на словарь, который содержит глобальные переменные функций - глобальное пространство имен модуля, в котором определена функция.

Если вы погрузитесь в код PyQuery:

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))

Ответ 2

Другие правильно указывают, как this определяется внутри этой лямбда, о которой вы говорите.

Чтобы подробнее рассказать об этом, попробуйте следующий код:

>>> 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() globals.
>>> f()
whoa!!

Это именно то, что происходит там. В строке 496 вы увидите следующий код:

for i, this in enumerate(self):             #this is the current object/node.
     func_globals(selector)['this'] = this  #func_globals returns selector.__globals__

Ответ 3

Это не бросает NameError, потому что переменная может существовать в момент вызова фактической функции.

>>> 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

Вышеописанная информация не является ошибкой, пока вы не вызовете функцию, которую вы отложили. В приведенном ниже примере кода они вручную устанавливают переменную с именем this в func_globals перед вызовом функции лямбда selector.