0

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

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调用都给出 a 。例如:NameError: name 'user_func5' is not defined

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

4

1 回答 1

1

此答案在线提供了修复#1的实时版本,您可以自己尝试

问题

没错,这不起作用的原因是范围问题。您可以通过检查文档来eval了解发生了什么:

eval(表达式,全局 = 无,本地 = 无)

...如果省略了两个字典[即全局和局部],则表达式将在调用 eval() 的环境中执行。

因此,可以合理地假设您遇到的问题取决于被调用的内容globalslocals上下文(即在 的定义(可能是单独的模块)内ClassTest) 。eval由于eval被调用的上下文通常不是您定义和/或导入的上下文,因此就相关而言user_func, user_func2....,这些函数是未定义的eval。这种思路得到了以下文档的支持globals

全局变量()

...这始终是当前模块的字典(在函数或方法中,这是定义它的模块,而不是调用它的模块)。

修复

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

locals['user_func'] 

代替:

eval('user_func')

修复 #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,您制作的导入测试现在将按预期运行。

优点

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

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

缺点

修复 #2

修复 #2 与修复 #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(),因此可能比修复 #1 更具性能/便携性。

缺点

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

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

于 2018-12-08T02:19:21.420 回答