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


вы можете динамически добавлять 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, the 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. Сделайте синтаксис менее подробным, мы используем Class decorator

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

  • определите дочерний класс ChildClass of 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')

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

вам нужно инициализировать класс variable=a() вызов __init__ 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'