Как я могу динамически создавать методы класса для класса в 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'

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


Редактировать:

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

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 ответов

Решение

Вы можете динамически добавить метод класса к классу простым присваиванием объекту класса или 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 и позвонить без проблем, но self аргумент 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()

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

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

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

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

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

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

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

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

Сначала мы определим такой 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)

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

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. Сделать синтаксис менее многословным, мы используем декоратор класса

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

  • определить дочерний класс 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 класс не является обязательным, в то время как @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')

Я использую 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'
Другие вопросы по тегам