установка импортируемых функций в качестве членов в статическом словаре - PullRequest
0 голосов
/ 08 декабря 2018

Существует простой класс, в котором я хочу хранить некоторые функции статически в словаре, используя различные способы:

import os, sys
class ClassTest():
    testFunc = {}
    def registerClassFunc(self,funcName):
        ClassTest.testFunc[funcName] = eval(funcName)
    @classmethod
    def registerClassFuncOnClass(cls,funcName):
        cls.testFunc[funcName] = eval(funcName)
    @staticmethod
    def registerClassFuncFromStatic(funcName):
        ClassTest.testFunc[funcName] = eval(funcName)

Некоторые примеры методов:

def user_func():
    print("I run therefore I am self-consistent")
def user_func2():
    print("I am read therefore I am interpreted")
def user_func3():
    print("I am registered through a meta function therefore I am not recognized")
def user_func4():
    print("I am registered through an instance function therefore I am not recognized")
def user_func5():
    print("I am registered through a static function therefore I am not recognized")

И небольшой тест:

if __name__ == "__main__":
    a = ClassTest()
    a.testFunc["user_func"] = user_func
    a.testFunc["user_func"]()
    a.testFunc["user_func2"] = eval("user_func2")
    a.testFunc["user_func2"]()

    ClassTest.testFunc["user_func"] = user_func
    ClassTest.testFunc["user_func"]()
    ClassTest.testFunc["user_func2"] = eval("user_func2")
    ClassTest.testFunc["user_func2"]()

    a.registerClassFunc("user_func5")  # does not work on import
    a.testFunc["user_func5"]()
    ClassTest.registerClassFuncFromStatic("user_func3") # does not work on import
    ClassTest.testFunc["user_func3"]()
    ClassTest.registerClassFuncOnClass("user_func4") # does not work on import
    ClassTest.testFunc["user_func4"]()

Все это работает при условии все эти элементы находятся в одном файле.Как только функциональность разделена на 2 файла и основной файл:

from ClassTest import ClassTest
from UserFunctions import user_func,user_func2, user_func3, user_func4, user_func5
if __name__ == "__main__":
    a = ClassTest()
    a.testFunc["user_func"] = user_func
    ...

Работают только первые два (непосредственно настраивая функцию), остальные - используя функцию, чтобы сделать то же самое -дать NameError на все eval звонки.Например: NameError: name 'user_func5' is not defined.

Какова здесь логика потери контекста при использовании методов вместо прямой установки функций?И можно ли заставить его работать, используя импорт из других пакетов, чтобы я мог поместить любую функцию в классе с помощью метода, а не напрямую?

1 Ответ

0 голосов
/ 08 декабря 2018

Существует живая версия исправления № 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 метод с этого момента.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...