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



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

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

与直接设置函数相比,使用方法时失去作用域的逻辑是什么?我可以使用从其他包导入的方法使它工作吗?这样我就可以用方法而不是直接在类中放置任何函数了?

这个答案中有一个实时版本的修复#1,您可以自己尝试

问题

你说得对,这不起作用的原因是范围界定问题。你可以通过仔细检查eval:的文档来了解发生了什么

eval(表达式,全局=无,局部=无(

如果省略了两个字典[ie-globals和locals],则表达式将在调用eval((的环境中执行。

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

globals((

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

修复

对于如何修复此代码,您有几个不同的选项。所有这些都将涉及将locals从您调用的上下文(例如ClassTest.registerClassFunc(传递到定义该方法的上下文。此外,您应该利用这个机会从代码中排除eval的使用(它的使用被认为是不好的做法,这是一个巨大的安全漏洞,yadda yadda(。假设locals是定义user_func的范围的dict,那么您总是可以执行以下操作:

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定义,那么您编写的导入测试现在将按预期运行。

优点

  • 提供了最初想要的功能。

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

缺点

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

  • inspect.currentframe()只能保证在CPython上工作。当使用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(),因此可能比修复程序#1更具性能/可移植性

缺点

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

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

最新更新