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?
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
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))
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_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 AttributeErrorstaticmethod
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()
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()
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'
Maintenant, si nous avons le décalque MethodPatcher
, nous devons faire deux choses:
ChildClass
de ModelPatcher
qui contient les méthodes supplémentaires à ajouterChildClass.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')
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
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')
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
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'