将导入的函数设置为静态字典中的成员

setting imported functions as members in a static dictionary

有一个简单的 class,我想使用不同的方式将一些函数静态存储在字典中:

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

只有前两个继续工作(直接设置函数),其他 - 使用函数做同样的事情 - 在所有 eval 调用中给出 NameError。例如:NameError: name 'user_func5' is not defined.

使用方法与直接设置函数时范围丢失的逻辑是什么?我可以使用其他包的导入来让它工作,这样我就可以使用方法而不是直接将任何函数放在 class 中吗?

There's a live version of fix #1 from this answer online that you can try out for yourself

问题

你说得对,这不起作用的原因是范围界定问题。您可以通过仔细检查 docs for eval:

来弄清楚发生了什么

eval(expression, globals=None, locals=None)

...If both dictionaries [ie globals and locals] are omitted, the expression is executed in the environment where eval() is called.

因此,可以合理地假设您遇到的问题归结为上下文中 globalslocals 的内容(即在定义中(并且可能是单独的模块) ClassTest),其中 eval 被调用。由于调用 eval 的上下文通常不是您定义 and/or 导入 user_func, user_func2.... 的上下文,因此这些函数在 eval 之前是未定义的被关注到。这种思路得到了 docs for globals:

的支持

globals()

...This is always the dictionary of the current module (inside a function or method, this is the module where it is defined, not the module from which it is called).

修复

对于如何修复此代码,您有几种不同的选择。所有这些都将涉及将 locals 从您调用的上下文传递,例如 ClassTest.registerClassFunc 到定义该方法的上下文。此外,您应该借此机会从您的代码中排除 eval 的使用(它的使用被认为是不好的做法,它是 massive security hole,yadda yadda yadda)。鉴于 locals 是定义了 user_func 的范围的字典,你总是可以这样做:

locals['user_func'] 

而不是:

eval('user_func')

修复 #1

Link to live version of this fix

这将是最容易实施的修复程序,因为它只需要对 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 定义,您编写的导入测试现在将按预期运行。

优点

  • 提供完全符合最初预期的功能。

  • 不涉及对函数签名的更改。

缺点

  • 调用 inspect.currentframe() 会导致性能下降,因此如果您计划调用 ClassTest 百万的方法,您可能无法使用此修复程序每秒一次。

  • inspect.currentframe() 只能保证在 CPython 上工作。 Mileage may vary when running this code with other implementations of Python.

修复#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())

优点

  • 不依赖于 inspect.currentframe(),因此可能 performant/portable 比修正 #1 更重要。

缺点

  • 您必须修改方法签名,因此您还必须更改使用这些方法的任何现有代码。

  • 从现在开始,您必须将 locals() 样板文件添加到每个 ClassTest 方法的每次调用中。