How can I dynamically create class methods for a class in python
如果我将一个小python程序定义为
1 2 3 4 5 6 7 8 9 10 | class a(): def _func(self): return"asdf" # Not sure what to resplace __init__ with so that a.func will return asdf def __init__(self, *args, **kwargs): setattr(self, 'func', classmethod(self._func)) if __name__ =="__main__": a.func |
我收到回溯错误
1 2 3 4 | Traceback (most recent call last): File"setattr_static.py", line 9, in <module> a.func AttributeError: class a has no attribute 'func' |
我想知道的是,如何在不实例化对象的情况下动态地将类方法设置为类?
编辑:这个问题的答案是
1 2 3 4 5 6 7 8 9 10 11 | class a(): pass def func(cls, some_other_argument): return some_other_argument setattr(a, 'func', classmethod(func)) if __name__ =="__main__": print(a.func) print(a.func("asdf")) |
返回以下输出
1 2 | <bound method type.func of <class '__main__.a'>> asdf |
您可以通过向类对象简单地赋值或通过类对象上的setattr动态地将类方法添加到类中。这里我使用的是以大写字母开头的python约定,以减少混淆:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # define a class object (your class may be more complicated than this...) class A(object): pass # a class method takes the class object as its first variable def func(cls): print 'I am a class method' # you can just add it to the class if you already know the name you want to use A.func = classmethod(func) # or you can auto-generate the name and set it this way the_name = 'other_func' setattr(A, the_name, classmethod(func)) |
这里有几个问题:
__init__ 仅在创建实例(如obj = a() 时运行。这就是说,当你打a.func 的时候,setattr() 的电话没有发生。- 您不能直接从该类的方法中访问类的属性,因此,您需要使用
self._func 或self.__class__._func ,而不只是在__init__ 的内部使用_func 。 self 将是a 的一个实例,如果您在实例上设置了一个属性,它将只对该实例可用,而不对类可用。所以即使打电话给setattr(self, 'func', self._func) 之后,a.func 也会引起一个属性错误。- 使用
staticmethod 的方式不会做任何事情,staticmethod 将返回一个结果函数,它不会修改参数。因此,您需要类似于setattr(self, 'func', staticmethod(self._func)) 的东西(但考虑到上述评论,这仍然不起作用)
现在的问题是,你到底想做什么?如果在初始化实例时确实要向类添加属性,可以执行以下操作:
1 2 3 4 5 6 7 8 9 10 11 | class a(): def _func(self): return"asdf" def __init__(self, *args, **kwargs): setattr(self.__class__, 'func', staticmethod(self._func)) if __name__ == '__main__': obj = a() a.func a.func() |
然而,这仍然有点奇怪。现在,您可以访问
既然您试图动态地向类中添加一个函数,那么下面这样的内容可能更接近您实际要做的事情?
1 2 3 4 5 6 7 8 9 10 11 | class a(): pass def _func(): return"asdf" a.func = staticmethod(_func) # or setattr(a, 'func', staticmethod(_func)) if __name__ == '__main__': a.func a.func() |
1。基本思想:使用一个额外的类来保存方法
我找到了一个有意义的工作方式:
首先,我们定义这样一个基类:
1 2 3 4 5 6 7 | class MethodPatcher: @classmethod def patch(cls, target): for k in cls.__dict__: obj = getattr(cls, k) if not k.startswith('_') and callable(obj): setattr(target, k, obj) |
现在我们有了一个原始类:
1 2 3 | class MyClass(object): def a(self): print('a') |
然后定义要添加到新
(在本例中,不要使方法名以
1 2 3 | class MyPatcher(MethodPatcher): def b(self): print('b') |
然后呼叫:
1 | MyPatcher.patch(MyClass) |
因此,您将发现新方法
1 2 3 | obj = MyClass() obj.a() # which prints an 'a' obj.b() # which prints a 'b' |
2。使语法不那么冗长,我们使用类修饰器
如果我们有
- 定义
ModelPatcher 的子类ChildClass ,其中包含要添加的额外方法 - 呼叫
ChildClass.patch(TargetClass) 。
因此,我们很快发现第二步可以通过使用装饰器来简化:
我们定义一个装饰师:
1 2 3 4 | def patch_methods(model_class): def do_patch(cls): cls.patch(model_class) return do_patch |
我们可以像这样使用它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @patch_methods(MyClass) class MyClassPatcher(MethodPatcher): def extra_method_a(self): print('a', self) @classmethod def extra_class_method_b(cls): print('c', cls) # !!ATTENTION!! the effect on declaring staticmethod here may not work as expected: # calling this method on an instance will take the self into the first argument. # @staticmethod # def extra_static_method_c(): # print('c') |
三.裹在一起
因此,我们现在可以将
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # method_patcher.py class MethodPatcher: @classmethod def patch(cls, target): for k in cls.__dict__: obj = getattr(cls, k) if not k.startswith('_') and callable(obj): setattr(target, k, obj) def patch_methods(model_class): def do_patch(cls): cls.patch(model_class) return do_patch |
所以我们可以自由使用它:
1 | from method_patcher import ModelPatcher, patch_model |
4。最终解决方案:更简单的声明
很快我发现
1 2 3 4 5 6 7 | def patch_methods(model_class): def do_patch(cls): for k in cls.__dict__: obj = getattr(cls, k) if not k.startswith('_') and callable(obj): setattr(model_class, k, obj) return do_patch |
它的用法是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @patch_methods(MyClass) class MyClassPatcher: def extra_method_a(self): print('a', self) @classmethod def extra_class_method_b(cls): print('c', cls) # !!ATTENTION!! the effect on declaring staticmethod here may not work as expected: # calling this method on an instance will take the self into the first argument. # @staticmethod # def extra_static_method_c(): # print('c') |
你可以这样做
1 2 3 4 5 6 7 8 | class a(): def _func(self): return"asdf" setattr(a, 'func', staticmethod(a._func)) if __name__ =="__main__": a.func() |
你需要1[0]
需要初始化类
我使用的是python 2.7.5,我无法让上面的解决方案为我工作。这就是我最后得出的结论:
1 2 3 4 5 6 7 8 9 10 11 | # define a class object (your class may be more complicated than this...) class A(object): pass def func(self): print 'I am class {}'.format(self.name) A.func = func # using classmethod() here failed with: # AttributeError: type object '...' has no attribute 'name' |