Существует живая версия исправления № 1 из этого онлайн-ответа, которую вы можете попробовать сами.
Проблема
Вы правы, причина этогоне работает из-за проблем с областями видимости.Вы можете выяснить, что происходит, изучив документы для eval
:
eval (выражение, глобальные переменные = нет, локальные = нет)
...Если оба словаря [т.е. глобальные и локальные] опущены, выражение выполняется в среде, где вызывается eval ().
Таким образом, разумно предположить, что проблемаИмеется до содержания globals
и locals
в контексте (то есть в пределах определения (и, возможно, отдельного модуля) ClassTest
), в котором вызывается eval
.Поскольку контекст, в котором вызывается eval
, в общем случае не является контекстом, в котором вы определили и / или импортировали user_func, user_func2....
, эти функции не определены в отношении eval
.Эта линия мышления подкрепляется документами для globals
:
globals ()
... Это всегда словарь текущегомодуль (внутри функции или метода, это модуль, в котором он определен, а не модуль, из которого он вызывается).
Исправление
У вас есть несколько различных вариантово том, как вы можете исправить этот код.Все они будут включать передачу locals
из контекста, в котором вы вызываете, например, ClassTest.registerClassFunc
, в контекст, в котором определен этот метод.Кроме того, вы должны воспользоваться возможностью, чтобы исключить использование eval
из вашего кода (его использование считается плохой практикой, это огромная дыра в безопасности , yadda yadda yadda).Учитывая, что locals
является указанием области действия, в которой определено user_func
, вы всегда можете просто сделать:
locals['user_func']
вместо:
eval('user_func')
fix # 1
Ссылка на живую версию этого исправления
Это будет самое простое в реализации исправление, так как для него требуется всего несколько настроек в определениях методов ClassTest
(и никаких изменений в сигнатурах любых методов).Он основан на том факте, что можно использовать пакет inspect
внутри функции для непосредственного получения locals
контекста вызова:
import inspect
def dictsGet(s, *ds):
for d in ds:
if s in d:
return d[s]
# if s is not found in any of the dicts d, treat it as an undefined symbol
raise NameError("name %s is not defined" % s)
class ClassTest():
testFunc = {}
def registerClassFunc(self, funcName):
_frame = inspect.currentframe()
try:
_locals = _frame.f_back.f_locals
finally:
del _frame
ClassTest.testFunc[funcName] = dictsGet(funcName, _locals, locals(), globals())
@classmethod
def registerClassFuncOnClass(cls, funcName):
_frame = inspect.currentframe()
try:
_locals = _frame.f_back.f_locals
finally:
del _frame
cls.testFunc[funcName] = dictsGet(funcName, _locals, locals(), globals())
@staticmethod
def registerClassFuncFromStatic(funcName):
_frame = inspect.currentframe()
try:
_locals = _frame.f_back.f_locals
finally:
del _frame
ClassTest.testFunc[funcName] = dictsGet(funcName, _locals, locals(), globals())
Если вы используете приведенное выше определение ClassTest
подготовленный вами импортный тест теперь будет работать, как и ожидалось.
Плюсы
Обеспечивает именно те функции, которые первоначально были предназначены.
Не включает никаких изменений в сигнатурах функций.
Минусы
fix # 2
Fix # 2 в основном совпадает с fix #1, за исключением того, что в этой версии вы явно передаете locals
в методы ClassTest
в точке вызова.Например, в этом исправлении определение ClassTest.registerClassFunc
будет выглядеть следующим образом:
def registerClassFunc(self, funcName, _locals):
ClassTest.testFunc[funcName] = dictsGet(funcName, _locals, locals(), globals())
, и вы будете называть его в своем коде так:
a = ClassTest()
a.registerClassFunc("user_func5", locals())
Pros
- Не полагается на
inspect.currentframe()
, и поэтому, вероятно, более производительный / переносимый, чем исправление № 1.
Минусы
У вас естьчтобы изменить сигнатуры методов, вам также придется изменить любой существующий код, использующий эти методы.
Вам нужно будет добавлять шаблон locals()
к каждому вызову каждого ClassTest
метод с этого момента.