Как я могу динамически создавать методы класса для класса в Python [дубликат]


этот вопрос уже есть ответ здесь:

если я определяю небольшую программу python как

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

Я получаю ошибку трассировки

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

то, что я пытаюсь выяснить, как я могу динамически установить метод класса класса без создания экземпляра объекта?


Edit:

ответ на эту проблему

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

возвращает следующий результат

<bound method type.func of <class '__main__.a'>>
asdf
6   51   2013-07-29 20:42:53

6 ответов:

вы можете динамически добавить classmethod к классу простым назначением объекту класса или setattr на объекте класса. Здесь я использую соглашение python, что классы начинаются с заглавных букв, чтобы уменьшить путаницу:

# 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() звонка не было
  • вы не можете получить доступ к атрибутам класса из методов этого класса, вместо того, чтобы использовать просто _func внутри __init__ вам нужно будет использовать self._func или self.__class__._func
  • self будет экземпляр a, если вы установите атрибут на экземпляре он будет доступен только для этого экземпляра, а не для класса. Так что даже после звонка setattr(self, 'func', self._func),a.func поднимет AttributeError
  • используя staticmethod то, как ты есть, ничего не сделает,staticmethod возвращает результирующую функцию, она не изменяет аргумент. Так что вместо этого вы хотели бы что-то вроде setattr(self, 'func', staticmethod(self._func)) (но с учетом приведенных выше комментариев, это все равно не будет работать)

Итак, теперь вопрос в том, что вы на самом деле пытается сделать? Если вы действительно хотите добавить атрибут в класс при инициализации экземпляра, вы можете сделать что-то вроде следующего:

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.func всегда будет последним созданным экземпляром a. Я действительно не могу придумать ни одного разумного способа превратить метод экземпляра, такой как _func() в статический метод или метод класса класса.

поскольку вы пытаетесь динамически добавить функцию в класс, возможно, что ближе к тому, что вы на самом деле пытаетесь сделать?

class a():
    pass

def _func():
    return "asdf"

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

if __name__ == '__main__':
    a.func
    a.func()

вы можете сделать это таким образом

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

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

if __name__ == "__main__":
    a.func()

1. Основная идея: используйте дополнительный класс для хранения методов

я нашел осмысленный способ сделать работу:

во-первых, мы определяем такой базовый класс:

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)

теперь, когда у нас есть оригинальный класс:

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

затем мы определяем новый метод, который мы хотим добавить на новый Patcher класс:

(не делайте имя метода начинается с _ в этом случае)

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

затем звоните:

MyPatcher.patch(MyClass)

Итак, вы найдете новый способ b(self) добавляется к исходному MyClass:

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

2. Сделайте синтаксис менее подробным, мы используем класс decorator

теперь, если у нас есть MethodPatcher decalred, мы должны сделать две вещи:

  • определить дочерний класс ChildClass на ModelPatcher, который содержит дополнительные методы, чтобы добавить
  • вызов ChildClass.patch(TargetClass)

так что мы вскоре обнаружили, что второй шаг может упростите с помощью декоратора:

определим декоратора:

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

и мы можем использовать его как:

@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. Оберните вместе

Итак, теперь мы можем поставить определение MethodPatcher и patch_method в одном модуле:

# 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

так что мы можем использовать его свободно:

from method_patcher import ModelPatcher, patch_model

4. Окончательное решение: более простое объявление

вскоре я обнаружил, что MethodPatcher класс не nessesary, в то время как @patch_method декоратор может сделать работу, так что наконец-то нам нужно только 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

и использование становится:

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

вам нужно setattr(self, 'func', staticmethod(self._func))

вам нужно инициализировать класс variable=a() вызов __init__ Нет инициализации в статическом классе

Я использую Python 2.7.5, и я не смог получить вышеуказанные решения, работающие для меня. Вот что у меня получилось:

# 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'