How can I dynamically create class methods for a c

2019-01-13 01:25发布

This question already has an answer here:

If I define a little python program as

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

I receive the traceback error

Traceback (most recent call last):
  File "setattr_static.py", line 9, in <module>
    a.func
AttributeError: class a has no attribute 'func'

What I am trying to figure out is, how can I dynamically set a class method to a class without instantiating an object?


Edit:

The answer for this problem is

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"))

returns the following output

<bound method type.func of <class '__main__.a'>>
asdf

6条回答
劫难
2楼-- · 2019-01-13 01:48

You need to setattr(self, 'func', staticmethod(self._func))

You need to initialize class variable=a() to call __init__ There is no init in static class

查看更多
不美不萌又怎样
3楼-- · 2019-01-13 01:50

1. The basic idea: use an extra class to hold the methods

I found a meaningful way to do the work:

First, we define such a BaseClass:

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)

Now that we have an original class:

class MyClass(object):
    def a(self):
        print('a')

Then we define the new method which we want to add on a new Patcher class:

(Do not make the method name starts with an _ in this case)

class MyPatcher(MethodPatcher):
    def b(self):
        print('b')

Then call:

MyPatcher.patch(MyClass)

So, you'll find the new method b(self) is added to the original MyClass:

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

2. Make the syntax less verbose, we use class decorator

Now if we have the MethodPatcher decalred, we need to do two things:

  • define a child class ChildClass of ModelPatcher which contains the extra methods to add
  • call ChildClass.patch(TargetClass)

So we soon found that the second step can be simplified by using a decorator:

We define a decorator:

def patch_methods(model_class):
    def do_patch(cls):
        cls.patch(model_class)
    return do_patch

And we can use it like:

@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')

3. Wrap together

So, we can now put the definition of MethodPatcher and patch_method into a single module:

# 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

So we can use it freely:

from method_patcher import ModelPatcher, patch_model

4. Final solution: More simple declaration

Soon I found that the MethodPatcher class is not nessesary, while the @patch_method decorator can do the work, so FINALLY we only need a patch_method:

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

And the usage becomes:

@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')
查看更多
祖国的老花朵
4楼-- · 2019-01-13 01:50

I'm using Python 2.7.5, and I wasn't able to get the above solutions working for me. This is what I ended up with:

# 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'
查看更多
可以哭但决不认输i
5楼-- · 2019-01-13 01:51

You can dynamically add a classmethod to a class by simple assignment to the class object or by setattr on the class object. Here I'm using the python convention that classes start with capital letters to reduce confusion:

# 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))
查看更多
Rolldiameter
6楼-- · 2019-01-13 02:00

You can do it in this way

class a():
    def _func(self):
        return "asdf"

setattr(a, 'func', staticmethod(a._func))

if __name__ == "__main__":
    a.func()
查看更多
相关推荐>>
7楼-- · 2019-01-13 02:09

There are a couple of problems here:

  • __init__ is only run when you create an instance, e.g. obj = a(). This means that when you do a.func, the setattr() call hasn't happened
  • You cannot access the attributes of a class directly from within methods of that class, so instead of using just _func inside of __init__ you would need to use self._func or self.__class__._func
  • self will be an instance of a, if you set an attribute on the instance it will only be available for that instance, not for the class. So even after calling setattr(self, 'func', self._func), a.func will raise an AttributeError
  • Using staticmethod the way you are will not do anything, staticmethod will return a resulting function, it does not modify the argument. So instead you would want something like setattr(self, 'func', staticmethod(self._func)) (but taking into account the above comments, this still won't work)

So now the question is, what are you actually trying to do? If you really want to add an attribute to a class when initializing an instance, you could do something like the following:

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()

However, this is still kind of weird. Now you can access a.func and call it without any problems, but the self argument to a.func will always be the most recently created instance of a. I can't really think of any sane way to turn an instance method like _func() into a static method or class method of the class.

Since you are trying to dynamically add a function to the class, perhaps something like the following is closer to what you are actually trying to do?

class a():
    pass

def _func():
    return "asdf"

a.func = staticmethod(_func)  # or setattr(a, 'func', staticmethod(_func))

if __name__ == '__main__':
    a.func
    a.func()
查看更多
登录 后发表回答