Проблемы с пространством имен Python с параллельным ipython

Я начинаю экспериментировать с параллельными инструментами IPython и имею проблему. Я запускаю свои движки python с помощью:

ipcluster start -n 3

Тогда следующий код работает нормально:

from IPython.parallel import Client

def dop(x):
    rc = Client()
    dview = rc[:]
    dview.block=True
    dview.execute('a = 5')
    dview['b'] = 10
    ack = dview.apply(lambda x: a+b+x, x)
    return ack

ack = dop(27)
print ack

возвращает [42, 42, 42], как и следовало ожидать. Но если я сломаю код на разные файлы: dop.py:

from IPython.parallel import Client

def dop(x):
    rc = Client()
    dview = rc[:]
    dview.block=True
    dview.execute('a = 5')
    dview['b'] = 10
    print dview['a']
    ack = dview.apply(lambda x: a+b+x, x)
    return ack

и попробуйте следующее:

from dop import dop
ack = dop(27)
print ack

Я получаю ошибки от каждого движка:

[0:apply]: NameError: global name 'a' is not defined
[1:apply]: NameError: global name 'a' is not defined
[2:apply]: NameError: global name 'a' is not defined

Я не понимаю... почему я не могу поместить функцию в другой файл и импортировать ее?

Ответ 1

Быстрый ответ: украсьте свою функцию с помощью @interactive от IPython.parallel.util [1], если вы хотите, чтобы у нее был доступ к глобальному пространству имен движка:

from IPython.parallel.util import interactive
f = interactive(lambda x: a+b+x)
ack = dview.apply(f, x)

Фактическое объяснение:

пространство имен пользователей IPython по существу является модулем __main__. Здесь код запускается, когда вы выполняете execute('a = 5').

Если вы определяете функцию в интерактивном режиме, ее модуль также __main__:

lam = lambda x: a+b+x
lam.__module__
'__main__'

Когда Engine не выполняет инициализацию функции, он делает это в соответствующем глобальном пространстве имен для функционального модуля, поэтому функции, определенные в __main__ в вашем клиенте, также определены в __main__ в Engine и, следовательно, имеют доступ к a.

Как только вы поместите его в файл и импортируете, функции больше не привязаны к __main__, а модуль dop:

from dop import dop
dop.__module__
'dop'

Все функции, условно определенные в этом модуле (включая lambdas), будут иметь это значение, поэтому, когда они распаковываются в Engine, их глобальное пространство имен будет принадлежать модулю dop, а не __main__, поэтому ваш 'a' недоступен.

По этой причине IPython предоставляет простой декодер @interactive, который приводит к распаковке любой функции, как если бы она была определена в __main__, независимо от того, где фактически определена функция.

Для примера разницы возьмите этот dop.py:

from IPython.parallel import Client
from IPython.parallel.util import interactive

a = 1

def dop(x):
    rc = Client()
    dview = rc[:]
    dview['a'] = 5
    f = lambda x: a+x
    return dview.apply_sync(f, x)

def idop(x):
    rc = Client()
    dview = rc[:]
    dview['a'] = 5
    f = interactive(lambda x: a+x)
    return dview.apply_sync(f, x)

Теперь dop будет использовать 'a' из модуля dop, а idop будет использовать 'a' из ваших пространств имен движков. Единственное различие между ними состоит в том, что функция, переданная для применения, заверяется в @interactive:

from dop import dop, idop
print dop(5)  # 6
print idop(5) # 10

[1]: В IPython >= 0.13 (предстоящий выпуск) @interactive также доступен как from IPython.parallel import interactive, где он всегда должен был быть.