Лямбда-функция не может получить доступ к импорту вызывающей функции? - PullRequest
0 голосов
/ 10 сентября 2018

Я написал лямбда-функцию для обработки вызовов динамически загружаемой библиотеки, но мне кажется, что я сталкиваюсь с проблемами пространства имен при импорте.Мой тестовый пример ниже:

def dot(*args):
    ''' return a dot notation string
    '''
    return '.'.join(map(str, args))


def test(import_name, classname):
    exec('import ' + import_name)
    get_global = lambda glob: eval(dot(import_name, classname, glob))
    technodename = get_global('technodename')
    metalstack = get_global('metalstack')
    return technodename, metalstack

print(test('PROJECT', 'Quabanatu'))

ошибка, которая возникает при выполнении лямбда-функции:

NameError: name 'PROJECT' is not defined

однако, если я выполню:

    technodename = eval(dot(import_name, classname, 'metalstack' ))

вместоВызов get_global работает просто отлично.Также, если я импортирую библиотеку PROJECT в начале программы, лямбда-функция будет работать нормально.Чего мне не хватает?

Кстати, я знаю, что это не лучший способ сделать это в этом случае, так как «technodname» является константой, но в некоторых моих кодах я вынужден искать переменную класса, основанную надругая переменная, следовательно, необходимо использовать функцию eval.

Ответы [ 2 ]

0 голосов
/ 10 сентября 2018

Конкретная причина этой проблемы заключается в том, как вы вызывали exec внутри функции.Когда exec используется в форме с одним аргументом, он использует локальные параметры вызывающего контекста.Это хорошо для модулей и классов, но усложняется внутри функции.exec работает с локальными переменными, которые хранятся в dict (как и модули и классы).Однако локальные функции хранятся по-разному [1].Таким образом, exec должен создать представление словаря для локальных функций и работать с ним.Это означает, однако, что любые изменения в этом словаре не отражаются в фактических локальных значениях функции.

def f():
    # NB. do not rely on any behaviour that results from mutating the dict returned by locals
    x = 'value' 
    assert locals()['x'] == 'value'
    assert 'builtins' not in locals()
    exec('import builtins')
    try:
        builtins
    except NameError as e:
        print(e)
    else:
        assert False
    print(locals()['builtins'])

# prints
# name 'builtins' is not defined
# <module 'builtins' (built-in)>

Правильное решение в вашем случае дано bruno desthuilliers.Однако, чтобы exec и eval работали надежным образом, вы должны предоставить глобальные и локальные данные для их использования, где это возможно.Это могут быть любые словари, которые вы хотите, а не только глобальные или локальные элементы вашего контекста.

например.

def g(module_name, attr):
    locals_ = {}
    globals_ = {}
    # two-arg form where locals and globals are the same
    exec('import ' + module_name, globals_)
    assert module_name not in locals_
    assert module_name in globals_
    exec('result = {}.{}'.format(module_name, attr), globals_, locals_)
    assert 'result' in locals_
    assert 'result' not in globals_
    return eval('{}.{}'.format(module_name, attr), globals_)

assert g('builtins', 'str') is str

[1] Они хранятся в массиве фиксированной длины, иИндекс для каждой локальной переменной вычисляется во время компиляции функции.Функции не имеют переменных локальных переменных - у них нет места для их хранения и нет способа узнать, как их получить.

0 голосов
/ 10 сентября 2018

Извините, но: вы делаете это неправильно

Как правило, когда вы набираете eval() или exec, вы делаете это неправильно (реальный вариант использования этих "функций" настолько редок, что я никогда не нуждался в них в течение 20 лет). Большинство операторов python являются исполняемыми (они выполняются во время выполнения) и являются синтаксическим сахаром для функций, предоставляемых как часть встроенных функций или в stdlib.

В вашем случае правильные решения здесь import_lib.import_module(module_name) и getattr(obj, attrname):

import importlib

def get_global(module, *names):
   obj = module
   for name in names:
       obj = getattr(obj, name)
   return obj

def test(module_name, classname):
    module = importlib.import_module(module_name)
    technodename = get_global(module, classname, "technodename")
    metalstack = get_global(module, classname, "metalstack")
    return technodename, metalstack
...