如何从类中的方法动态创建模块级函数
我正在尝试根据一个类中的方法动态创建模块级别的函数。也就是说,对于类中的每一个方法,我想创建一个同名的函数,这个函数会实例化这个类,然后调用相应的方法。
我这样做的原因是想以面向对象的方式来创建Fabric文件。因为Fabric只能调用模块级别的函数,而不能直接调用类的方法,所以我需要这样一个变通办法。
我参考了以下链接来入门:
- 如何获取Python类中的方法列表?
- 如何动态添加函数到Python模块?
- 如何在当前模块上调用setattr()?
- Python的getattr函数
- 如何通过字符串调用模块中的函数?
- 如何修改Python中的本地命名空间?
我写出了以下代码:
import inspect
import sys
import types
class TestClass(object):
def __init__(self):
pass
def method1(self, arg1):
print 'method 1 %s' % arg1
def method2(self):
print 'method 2'
def fabric_class_to_function_magic(module_name):
# get the module as an object
print module_name
module_obj = sys.modules[module_name]
print dir(module_obj)
# Iterate over the methods of the class and dynamically create a function
# for each method that calls the method and add it to the current module
for method in inspect.getmembers(TestClass, predicate=inspect.ismethod):
print
print method
method_name, method_obj = method
# create a new template function which calls the method
def newfunc_template(*args, **kwargs):
tc = TestClass()
func = getattr(tc, method_name)
return func(*args, **kwargs)
# create the actual function
print 'code: ', newfunc_template.func_code
print 'method_name: ', method_name
newfunc = types.FunctionType(newfunc_template.func_code,
{'TestClass': TestClass,
'getattr': getattr,
'method_name': method_name,
},
name=method_name,
argdefs=newfunc_template.func_defaults,
closure=newfunc_template.func_closure,
)
# add the new function to the current module
setattr(module_obj, method_name, newfunc)
# test the dynamically created module level function
thismodule = sys.modules[__name__]
print dir(thismodule)
fabric_class_to_function_magic(__name__)
print dir(thismodule)
method1('arg1')
method2()
但是我遇到了以下错误:
['TestClass', '__builtins__', '__doc__', '__file__', '__name__', '__package__', 'fabric_class_to_function_magic', 'inspect', 'sys', 'thismodule', 'types']
__main__
['TestClass', '__builtins__', '__doc__', '__file__', '__name__', '__package__', 'fabric_class_to_function_magic', 'inspect', 'sys', 'thismodule', 'types']
('__init__', <unbound method TestClass.__init__>)
code: <code object newfunc_template at 0x7f8800a28d50, file "test.py", line 85>
method_name: __init__
('method1', <unbound method TestClass.method1>)
code: <code object newfunc_template at 0x7f8800a28d50, file "test.py", line 85>
method_name: method1
('method2', <unbound method TestClass.method2>)
code: <code object newfunc_template at 0x7f8800a28d50, file "test.py", line 85>
method_name: method2
['TestClass', '__builtins__', '__doc__', '__file__', '__init__', '__name__', '__package__', 'fabric_class_to_function_magic', 'inspect', 'method1', 'method2', 'sys', 'thismodule', 'types']
Traceback (most recent call last):
File "test.py", line 111, in <module>
method1('arg1')
File "test.py", line 88, in newfunc_template
return func(*args, **kwargs)
TypeError: method2() takes exactly 1 argument (2 given)
看起来是重复使用了函数的引用?有什么想法吗?
更新:这是经过Ned Batchelder修复后的工作代码:
def fabric_class_to_function_magic(module_name):
# get the module as an object
module_obj = sys.modules[module_name]
# Iterate over the methods of the class and dynamically create a function
# for each method that calls the method and add it to the current module
for method in inspect.getmembers(TestClass, predicate=inspect.ismethod):
method_name, method_obj = method
# get the bound method
tc = TestClass()
func = getattr(tc, method_name)
# add the function to the current module
setattr(module_obj, method_name, func)
更新2:这是我关于这个主题的博客文章:http://www.saltycrane.com/blog/2010/09/class-based-fabric-scripts-metaprogramming-hack/
3 个回答
0
这可能对某些人有用:
import inspect
import sys
def get_functions_from_class_in_module_and_extract_to_module(module, *args, class_config={}, **kwargs):
classes = dict(tuple(x) for x in inspect.getmembers(sys.modules[module.__name__], inspect.isclass))
for cls_name, cls in classes.items():
class_instance = cls(**class_config[cls_name])
functions = dict(tuple(x) for x in inspect.getmembers(cls, inspect.isfunction))
for function_name, func in functions.items():
class_func = lambda *args, **kwargs: func(class_instance, *args, **kwargs)
setattr(module, function_name, class_func)
1
其实你的代码是对的,但当执行 return func(*args, **kwargs) 时,args 会传递一个空的元组,比如 (),而你的 method2 没有任何参数,所以就会出现这样的错误。
解决这个问题的一个简单方法是,像下面这样:
class TestClass(object):
def __init__(self):
pass
def method1(self, arg1):
print 'method 1 %s' % arg1
def method2(self, *args, **kw):
print 'method 2'
9
你可能想得太复杂了。把fabric_class_to_function_magic
的结尾改成这样:
tc = TestClass()
func = getattr(tc, method_name)
# add the new function to the current module
setattr(module_obj, method_name, func)
这样就可以正常工作了。其实不需要再创建一个新的函数对象,因为你已经通过getattr从你的对象中得到了一个。通过getattr返回的绑定方法是可以调用的东西。只需要把它赋值给你的模块属性,就可以了。