如何在python中为类动态创建类方法

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._funcself.__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()

然而,这仍然有点奇怪。现在,您可以访问a.func并毫无问题地调用它,但是a.funcself参数将始终是最近创建的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')

然后定义要添加到新Patcher类中的新方法:

(在本例中,不要使方法名以_开头)

1
2
3
class MyPatcher(MethodPatcher):
    def b(self):
        print('b')

然后呼叫:

1
MyPatcher.patch(MyClass)

因此,您将发现新方法b(self)添加到原始MyClass中:

1
2
3
obj = MyClass()
obj.a()  # which prints an 'a'
obj.b()  # which prints a 'b'

2。使语法不那么冗长,我们使用类修饰器

如果我们有MethodPatcher牌,我们需要做两件事:

  • 定义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')

三.裹在一起

因此,我们现在可以将MethodPatcherpatch_method的定义放在一个模块中:

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。最终解决方案:更简单的声明

很快我发现MethodPatcher类是不必要的,而@patch_method装饰师可以完成这项工作,所以最后我们只需要一个patch_method类:

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]

需要初始化类variable=a()来调用__init__。静态类中没有init


我使用的是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'