web-dev-qa-db-fra.com

Comment puis-je créer dynamiquement des méthodes de classe pour une classe dans python

Si je définis un petit programme python comme

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 __== "__main__":
    a.func

Je reçois l'erreur de retraçage

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

Ce que j'essaie de comprendre, c'est comment puis-je définir dynamiquement une méthode de classe sur une classe sans instancier un objet?


Modifier:

La réponse à ce problème est

class a():
    pass

def func(cls, some_other_argument):
    return some_other_argument

setattr(a, 'func', classmethod(func))

if __== "__main__":
    print(a.func)
    print(a.func("asdf"))

renvoie la sortie suivante

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

Vous pouvez ajouter dynamiquement une méthode de classe à une classe par simple affectation à l'objet classe ou par setattr sur l'objet classe. Ici, j'utilise la convention python que les classes commencent par des majuscules pour réduire la 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))
63
tdelaney

Il y a quelques problèmes ici:

  • __init__ N'est exécuté que lorsque vous créez une instance, par exemple obj = a(). Cela signifie que lorsque vous faites a.func, L'appel setattr() ne s'est pas produit
  • Vous ne pouvez pas accéder aux attributs d'une classe directement à partir des méthodes de cette classe, donc au lieu d'utiliser simplement _func À l'intérieur de __init__, Vous devrez utiliser self._func Ou self.__class__._func
  • self sera une instance de a, si vous définissez un attribut sur l'instance, il ne sera disponible que pour cette instance, pas pour la classe. Ainsi, même après avoir appelé setattr(self, 'func', self._func), a.func Lèvera une AttributeError
  • Utiliser staticmethod comme vous ne ferez rien, staticmethod retournera une fonction résultante, il ne modifie pas l'argument. Donc, à la place, vous voudriez quelque chose comme setattr(self, 'func', staticmethod(self._func)) (mais en tenant compte des commentaires ci-dessus, cela ne fonctionnera toujours pas)

Alors maintenant, la question est, qu'essayez-vous réellement de faire? Si vous voulez vraiment ajouter un attribut à une classe lors de l'initialisation d'une instance, vous pouvez faire quelque chose comme ceci:

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

    def __init__(self, *args, **kwargs):
        setattr(self.__class__, 'func', staticmethod(self._func))

if __== '__main__':
    obj = a()
    a.func
    a.func()

Cependant, c'est toujours un peu bizarre. Vous pouvez maintenant accéder à a.func Et l'appeler sans problème, mais l'argument self à a.func Sera toujours l'instance la plus récemment créée de a. Je ne peux pas vraiment penser à une manière saine de transformer une méthode d'instance comme _func() en une méthode statique ou une méthode de classe de la classe.

Puisque vous essayez d'ajouter dynamiquement une fonction à la classe, peut-être quelque chose comme ce qui suit est plus proche de ce que vous essayez réellement de faire?

class a():
    pass

def _func():
    return "asdf"

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

if __== '__main__':
    a.func
    a.func()
8
Andrew Clark

Vous pouvez le faire de cette façon

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

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

if __== "__main__":
    a.func()
2
eri

1. L'idée de base: utiliser une classe supplémentaire pour contenir les méthodes

J'ai trouvé un moyen significatif de faire le travail:

Tout d'abord, nous définissons une telle 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)

Maintenant que nous avons une classe originale:

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

Ensuite, nous définissons la nouvelle méthode que nous voulons ajouter sur une nouvelle classe Patcher:

(Ne faites pas que le nom de la méthode commence par un _ Dans ce cas)

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

Appelez ensuite:

MyPatcher.patch(MyClass)

Ainsi, vous trouverez la nouvelle méthode b(self) ajoutée à l'original MyClass:

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

2. Rendez la syntaxe moins verbeuse, nous utilisons le décorateur de classe

Maintenant, si nous avons le décalque MethodPatcher, nous devons faire deux choses:

  • définir une classe enfant ChildClass de ModelPatcher qui contient les méthodes supplémentaires à ajouter
  • appeler ChildClass.patch(TargetClass)

Nous avons donc rapidement constaté que la deuxième étape peut être simplifiée en utilisant un décorateur:

Nous définissons un décorateur:

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

Et nous pouvons l'utiliser comme:

@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. Envelopper ensemble

Ainsi, nous pouvons maintenant mettre la définition de MethodPatcher et patch_method Dans un seul 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

Nous pouvons donc l'utiliser librement:

from method_patcher import ModelPatcher, patch_model

4. Solution finale: déclaration plus simple

Bientôt, j'ai trouvé que la classe MethodPatcher n'est pas nécessaire, tandis que le décorateur @patch_method Peut faire le travail, donc ENFIN nous seulement besoin d'un 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

Et l'usage devient:

@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')
2
Alfred Huang

Vous devez setattr(self, 'func', staticmethod(self._func))

Vous devez initialiser la classe variable=a() pour appeler __init__ Il n'y a pas d'init dans la classe statique

1
eri

J'utilise Python 2.7.5, et je n'ai pas pu faire fonctionner les solutions ci-dessus pour moi. C'est ce que j'ai fini avec:

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