Ajouter une méthode à une Instance D'objet existante

j'ai lu qu'il est possible d'ajouter une méthode à un objet existant (i.e., pas dans la définition de classe) en Python.

je comprends qu'il n'est pas toujours bon de le faire. Mais comment peut-on faire cela?

516
demandé sur Paul Ratazzi 2008-08-04 06:17:51

17 réponses

en Python, il y a une différence entre les fonctions et les méthodes liées.

>>> def foo():
...     print "foo"
...
>>> class A:
...     def bar( self ):
...         print "bar"
...
>>> a = A()
>>> foo
<function foo at 0x00A98D70>
>>> a.bar
<bound method A.bar of <__main__.A instance at 0x00A9BC88>>
>>>

les méthodes liées ont été" liées " (comment descriptives) à une instance, et cette instance sera transmise comme premier argument chaque fois que la méthode est appelée.

les Callables qui sont des attributs d'une classe (par opposition à une instance) sont toujours non liés, donc vous pouvez modifier la définition de classe quand vous voulez:

>>> def fooFighters( self ):
...     print "fooFighters"
...
>>> A.fooFighters = fooFighters
>>> a2 = A()
>>> a2.fooFighters
<bound method A.fooFighters of <__main__.A instance at 0x00A9BEB8>>
>>> a2.fooFighters()
fooFighters

les instances définies précédemment sont également mises à jour (tant qu'elles n'ont pas dépassé l'attribut lui-même):

>>> a.fooFighters()
fooFighters

le problème vient quand vous voulez attacher une méthode à une instance simple:

>>> def barFighters( self ):
...     print "barFighters"
...
>>> a.barFighters = barFighters
>>> a.barFighters()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: barFighters() takes exactly 1 argument (0 given)

la fonction n'est pas automatiquement liée lorsqu'elle est attachée directement à une instance:

>>> a.barFighters
<function barFighters at 0x00A98EF0>

pour le lier, nous pouvons utiliser la fonction MethodType dans le module types :

>>> import types
>>> a.barFighters = types.MethodType( barFighters, a )
>>> a.barFighters
<bound method ?.barFighters of <__main__.A instance at 0x00A9BC88>>
>>> a.barFighters()
barFighters

cette fois, les autres cas de la classe n'ont pas été affectés:

>>> a2.barFighters()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: A instance has no attribute 'barFighters'

pour plus d'information, lisez à propos de descripteurs et metaclass programmation .

773
répondu Jason Pratt 2011-09-24 23:47:39

Module nouveau est déprécié depuis python 2.6 et supprimé en 3.0, utilisez types

voir http://docs.python.org/library/new.html

dans l'exemple ci-dessous, j'ai délibérément supprimé la valeur de retour de la fonction patch_me() . Je pense que donner la valeur de retour peut faire croire que le patch renvoie un nouvel objet, qui n'est pas vrai - il modifie l'arrivée. Probablement cela peut faciliter une utilisation plus disciplinée de l'observation de la Lune.

import types

class A(object):#but seems to work for old style objects too
    pass

def patch_me(target):
    def method(target,x):
        print "x=",x
        print "called from", target
    target.method = types.MethodType(method,target)
    #add more if needed

a = A()
print a
#out: <__main__.A object at 0x2b73ac88bfd0>  
patch_me(a)    #patch instance
a.method(5)
#out: x= 5
#out: called from <__main__.A object at 0x2b73ac88bfd0>
patch_me(A)
A.method(6)        #can patch class too
#out: x= 6
#out: called from <class '__main__.A'>
82
répondu Evgeny 2013-02-07 13:22:40

préface-une note sur la compatibilité: d'autres réponses ne peuvent fonctionner qu'en Python 2 - cette réponse devrait parfaitement fonctionner en Python 2 et 3. Si vous n'écrivez que Python 3 , vous pourriez omettre explicitement d'hériter de object , mais sinon le code devrait rester le même.

ajouter une méthode à une Instance D'objet existante

j'ai lu qu'il est possible d'ajouter une méthode à un objet existant (par exemple, pas dans le définition de classe) en Python.

je comprends qu'il n'est pas toujours une bonne décision de le faire. mais, comment faire cela?

Oui, c'est possible - mais pas recommandé

Je ne recommande pas cela. C'est une mauvaise idée. Ne pas le faire.

voici quelques raisons:

  • vous ajouterez un objet lié à chaque instance vous faites cela. Si vous faites cela, vous aurez probablement perdre beaucoup de mémoire. Les méthodes reliées ne sont généralement créées que pour la courte durée de leur appel, et elles cessent d'exister lorsque les déchets sont automatiquement ramassés. Si vous faites cela manuellement, vous aurez un lien de nom renvoyant à la méthode liée - qui empêchera sa collecte des ordures sur l'utilisation.
  • les instances D'objet d'un type donné ont généralement leurs méthodes sur tous les objets de ce type. Si vous ajoutez des méthodes ailleurs, certaines instances auront ces méthodes et d'autres non. Les programmeurs ne s'y attendront pas, et vous risquez de violer la règle de la moindre surprise .
  • Puisqu'il y a d'autres bonnes raisons de ne pas le faire, vous vous donnerez en outre une mauvaise réputation si vous le faites.

ainsi, je suggère que vous ne faites pas cela à moins que vous avez une bonne raison. il est beaucoup mieux de définir la méthode correcte dans la définition de la classe ou moins de préférence à patch de singe la classe directement, comme ceci:

Foo.sample_method = sample_method

Puisque c'est instructif, cependant, je vais vous montrer quelques façons de le faire.

Comment cela peut être fait

voici du code d'installation. Nous avons besoin d'une définition de classe. Ça peut être importé, mais ça n'a pas d'importance.

class Foo(object):
    '''An empty class to demonstrate adding a method to an instance'''

Créer une instance:

foo = Foo()

créer une méthode pour y ajouter:

def sample_method(self, bar, baz):
    print(bar + baz)

méthode zéro (0) - utiliser la méthode du descripteur, __get__

recherche pointillée sur les fonctions appeler la méthode __get__ de la fonction avec l'instance, liant l'objet à la méthode et créant ainsi une méthode liée."

foo.sample_method = sample_method.__get__(foo)

et maintenant:

>>> foo.sample_method(1,2)
3
Méthode

l'un des types.MethodType

tout D'abord, importer les types, à partir de laquelle nous obtiendrons le constructeur de méthode:

import types

maintenant nous ajoutons la méthode à l'instance. Pour ce faire, nous avons besoin du constructeur MethodType du module types (que nous avons importé ci-dessus).

l'argument signature pour les types.MethodType est (function, instance, class) :

foo.sample_method = types.MethodType(sample_method, foo, Foo)

et usage:

>>> foo.sample_method(1,2)
3

deuxième méthode: reliure lexicale

tout d'abord, nous créons une fonction d'enrubannage qui lie la méthode à l'instance:

def bind(instance, method):
    def binding_scope_fn(*args, **kwargs): 
        return method(instance, *args, **kwargs)
    return binding_scope_fn

utilisation:

>>> foo.sample_method = bind(foo, sample_method)    
>>> foo.sample_method(1,2)
3

Méthode 3: functools.partiels

une fonction partielle applique le(s) premier (s) argument (S) à une fonction (et éventuellement des arguments de mot-clé), et peut ensuite être appelée avec les arguments restants (et les arguments de mot-clé prépondérants). Ainsi:

>>> from functools import partial
>>> foo.sample_method = partial(sample_method, foo)
>>> foo.sample_method(1,2)
3    

cela a du sens quand on considère que les méthodes liées sont des fonctions partielles de l'instance.

Indépendant fonctionner comme un attribut de l'objet - pourquoi cela ne fonctionne pas:

si nous essayons d'ajouter la méthode sample_method de la même manière que nous pourrions l'ajouter à la classe, elle n'est pas liée à l'instance, et ne prend pas le soi implicite comme premier argument.

>>> foo.sample_method = sample_method
>>> foo.sample_method(1,2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: sample_method() takes exactly 3 arguments (2 given)

nous pouvons faire le la fonction unbound fonctionne en passant explicitement l'instance (ou quoi que ce soit, puisque cette méthode n'utilise pas réellement la variable d'argument self ), mais elle ne serait pas cohérente avec la signature attendue d'autres instances (si nous corrigeons cette instance en singe):

>>> foo.sample_method(foo, 1, 2)
3

Conclusion

Vous savez maintenant plusieurs façons de pourrait de le faire, mais en toute sincérité, - ne pas le faire.

54
répondu Aaron Hall 2018-09-12 15:06:04

je pense que les réponses ci-dessus ont manqué le point clé.

avons une classe avec une méthode:

class A(object):
    def m(self):
        pass

maintenant, jouons avec dans ipython:

In [2]: A.m
Out[2]: <unbound method A.m>

Ok, so m() devient en quelque sorte une méthode non liée de A . Mais est-ce vraiment comme ça?

In [5]: A.__dict__['m']
Out[5]: <function m at 0xa66b8b4>

il s'avère que m () est juste une fonction, de référence à laquelle est ajoutée à Un class dictionnaire - il n'y a pas de magie. Alors pourquoi A. m nous donne une méthode non liée? C'est parce que le point n'est pas traduit en une simple recherche de dictionnaire. C'est de facto un appel de classe A.___.__ getattribute_ _ (A, 'm'):

In [11]: class MetaA(type):
   ....:     def __getattribute__(self, attr_name):
   ....:         print str(self), '-', attr_name

In [12]: class A(object):
   ....:     __metaclass__ = MetaA

In [23]: A.m
<class '__main__.A'> - m
<class '__main__.A'> - m

maintenant, je ne suis pas sûr du haut de ma tête pourquoi la dernière ligne est imprimée deux fois, mais il est clair ce qui se passe là-bas.

maintenant, ce que fait par défaut __getattribute__ est qu'il vérifie si l'attribut est un soi-disant descripteur ou non, c'est-à-dire s'il implémente une méthode spéciale __get__. Si elle implémente cette méthode, alors ce qui est retourné est le résultat de l'appel de cette méthode __get__. Retour à la première version de notre a classe, voici ce que nous avons:

In [28]: A.__dict__['m'].__get__(None, A)
Out[28]: <unbound method A.m>

et parce que les fonctions Python implémentent le protocole du descripteur, si elles sont appelé, au nom d'un objet, ils se lient à cet objet dans leur __get__ méthode.

Ok, alors comment ajouter une méthode à un objet existant? En supposant que ça ne vous dérange pas de changer de classe, c'est aussi simple que:

B.m = m

puis B. m "devient" une méthode non liée, grâce au descripteur magic.

et si vous voulez ajouter une méthode à un seul objet, alors vous devez émuler la machine vous-même, en utilisant des types.MethodType:

b.m = types.MethodType(m, b)

Par la voie:

In [2]: A.m
Out[2]: <unbound method A.m>

In [59]: type(A.m)
Out[59]: <type 'instancemethod'>

In [60]: type(b.m)
Out[60]: <type 'instancemethod'>

In [61]: types.MethodType
Out[61]: <type 'instancemethod'>
31
répondu Tomasz Zielinski 2016-03-06 16:47:01

en Python Monkey correction fonctionne généralement en écrasant une classe ou des fonctions signature avec votre propre. Ci-dessous un exemple du Zope Wiki :

from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
   return "ook ook eee eee eee!"
SomeClass.speak = speak

ce code écrira/créera une méthode appelée speak on the class. Dans le de Jeff Atwood, un billet récent sur monkey patching . Il montre un exemple dans C # 3.0 qui est la langue courante que j'utilise pour le travail.

15
répondu John Downey 2008-08-04 02:31:13

il y a au moins deux façons d'attacher une méthode à une instance sans types.MethodType :

>>> class A:
...  def m(self):
...   print 'im m, invoked with: ', self

>>> a = A()
>>> a.m()
im m, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.m
<bound method A.m of <__main__.A instance at 0x973ec6c>>
>>> 
>>> def foo(firstargument):
...  print 'im foo, invoked with: ', firstargument

>>> foo
<function foo at 0x978548c>

1:

>>> a.foo = foo.__get__(a, A) # or foo.__get__(a, type(a))
>>> a.foo()
im foo, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.foo
<bound method A.foo of <__main__.A instance at 0x973ec6c>>

2:

>>> instancemethod = type(A.m)
>>> instancemethod
<type 'instancemethod'>
>>> a.foo2 = instancemethod(foo, a, type(a))
>>> a.foo2()
im foo, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.foo2
<bound method instance.foo of <__main__.A instance at 0x973ec6c>>

Liens utiles:

modèle de données - descripteurs d'invocation

descripteur HowTo Guide-invocating descriptors

9
répondu ndpu 2013-04-26 15:47:35

vous pouvez utiliser lambda pour lier une méthode à une instance:

def run(self):
    print self._instanceString

class A(object):
    def __init__(self):
        self._instanceString = "This is instance string"

a = A()
a.run = lambda: run(a)
a.run()

ici instance string

processus terminé avec le code de sortie 0

7
répondu Evgeny Prokurat 2014-07-21 12:55:54

Ce que vous cherchez est setattr je crois. Utilisez ceci pour définir un attribut sur un objet.

>>> def printme(s): print repr(s)
>>> class A: pass
>>> setattr(A,'printme',printme)
>>> a = A()
>>> a.printme() # s becomes the implicit 'self' variable
< __ main __ . A instance at 0xABCDEFG>
6
répondu HS. 2008-08-07 11:30:16

puisque cette question demandait des versions non-Python, voici JavaScript:

a.methodname = function () { console.log("Yay, a new method!") }
6
répondu Thom Blake 2012-03-09 15:07:08

la Consolidation de Jason Pratt et le wiki de la communauté réponses, avec un oeil sur les résultats de différentes méthodes de liaison:

en particulier noter comment l'ajout de la fonction de liaison comme une méthode de classe fonctionne , mais la portée de référence est incorrecte.

#!/usr/bin/python -u
import types
import inspect

## dynamically adding methods to a unique instance of a class


# get a list of a class's method type attributes
def listattr(c):
    for m in [(n, v) for n, v in inspect.getmembers(c, inspect.ismethod) if isinstance(v,types.MethodType)]:
        print m[0], m[1]

# externally bind a function as a method of an instance of a class
def ADDMETHOD(c, method, name):
    c.__dict__[name] = types.MethodType(method, c)

class C():
    r = 10 # class attribute variable to test bound scope

    def __init__(self):
        pass

    #internally bind a function as a method of self's class -- note that this one has issues!
    def addmethod(self, method, name):
        self.__dict__[name] = types.MethodType( method, self.__class__ )

    # predfined function to compare with
    def f0(self, x):
        print 'f0\tx = %d\tr = %d' % ( x, self.r)

a = C() # created before modified instnace
b = C() # modified instnace


def f1(self, x): # bind internally
    print 'f1\tx = %d\tr = %d' % ( x, self.r )
def f2( self, x): # add to class instance's .__dict__ as method type
    print 'f2\tx = %d\tr = %d' % ( x, self.r )
def f3( self, x): # assign to class as method type
    print 'f3\tx = %d\tr = %d' % ( x, self.r )
def f4( self, x): # add to class instance's .__dict__ using a general function
    print 'f4\tx = %d\tr = %d' % ( x, self.r )


b.addmethod(f1, 'f1')
b.__dict__['f2'] = types.MethodType( f2, b)
b.f3 = types.MethodType( f3, b)
ADDMETHOD(b, f4, 'f4')


b.f0(0) # OUT: f0   x = 0   r = 10
b.f1(1) # OUT: f1   x = 1   r = 10
b.f2(2) # OUT: f2   x = 2   r = 10
b.f3(3) # OUT: f3   x = 3   r = 10
b.f4(4) # OUT: f4   x = 4   r = 10


k = 2
print 'changing b.r from {0} to {1}'.format(b.r, k)
b.r = k
print 'new b.r = {0}'.format(b.r)

b.f0(0) # OUT: f0   x = 0   r = 2
b.f1(1) # OUT: f1   x = 1   r = 10  !!!!!!!!!
b.f2(2) # OUT: f2   x = 2   r = 2
b.f3(3) # OUT: f3   x = 3   r = 2
b.f4(4) # OUT: f4   x = 4   r = 2

c = C() # created after modifying instance

# let's have a look at each instance's method type attributes
print '\nattributes of a:'
listattr(a)
# OUT:
# attributes of a:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x000000000230FD88>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x000000000230FD88>>
# f0 <bound method C.f0 of <__main__.C instance at 0x000000000230FD88>>

print '\nattributes of b:'
listattr(b)
# OUT:
# attributes of b:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x000000000230FE08>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x000000000230FE08>>
# f0 <bound method C.f0 of <__main__.C instance at 0x000000000230FE08>>
# f1 <bound method ?.f1 of <class __main__.C at 0x000000000237AB28>>
# f2 <bound method ?.f2 of <__main__.C instance at 0x000000000230FE08>>
# f3 <bound method ?.f3 of <__main__.C instance at 0x000000000230FE08>>
# f4 <bound method ?.f4 of <__main__.C instance at 0x000000000230FE08>>

print '\nattributes of c:'
listattr(c)
# OUT:
# attributes of c:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x0000000002313108>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x0000000002313108>>
# f0 <bound method C.f0 of <__main__.C instance at 0x0000000002313108>>

personnellement, je préfère la route de la fonction ADDMETHOD externe, car elle me permet d'assigner dynamiquement de nouveaux noms de méthode dans un itérateur aussi bien.

def y(self, x):
    pass
d = C()
for i in range(1,5):
    ADDMETHOD(d, y, 'f%d' % i)
print '\nattributes of d:'
listattr(d)
# OUT:
# attributes of d:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x0000000002303508>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x0000000002303508>>
# f0 <bound method C.f0 of <__main__.C instance at 0x0000000002303508>>
# f1 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f2 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f3 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f4 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
5
répondu Nisan.H 2012-01-28 00:18:28

vous devriez vraiment regarder forbidden fruit , c'est une bibliothèque de python qui fournit un support pour les patches de singe N'importe quelle classe de python, même les cordes.

5
répondu Gabriel Falcão 2013-08-25 21:56:46

c'est en fait un addon à la réponse de "Jason Pratt"

Bien que Jasons réponse fonctionne, il fonctionne seulement si l'on veut ajouter une fonction à une classe. Il ne fonctionne pas pour moi quand j'ai essayé de recharger une méthode déjà existante de la .py fichier de code source.

il m'a fallu des siècles pour trouver une solution, mais l'astuce semble simple... 1.st importer le code à partir du fichier de code source 2.nd forcer un rechargement 3.rd types d'utilisation.FunctionType(...) de convertissez la méthode importée et liée en une fonction vous pouvez également transmettre les variables globales actuelles, car la méthode rechargée serait dans un espace de noms différent. 4.maintenant vous pouvez continuer comme suggéré par " Jason Pratt" en utilisant les types.MethodType(...)

exemple:

# this class resides inside ReloadCodeDemo.py
class A:
    def bar( self ):
        print "bar1"

    def reloadCode(self, methodName):
        ''' use this function to reload any function of class A'''
        import types
        import ReloadCodeDemo as ReloadMod # import the code as module
        reload (ReloadMod) # force a reload of the module
        myM = getattr(ReloadMod.A,methodName) #get reloaded Method
        myTempFunc = types.FunctionType(# convert the method to a simple function
                                myM.im_func.func_code, #the methods code
                                globals(), # globals to use
                                argdefs=myM.im_func.func_defaults # default values for variables if any
                                ) 
        myNewM = types.MethodType(myTempFunc,self,self.__class__) #convert the function to a method
        setattr(self,methodName,myNewM) # add the method to the function

if __name__ == '__main__':
    a = A()
    a.bar()
    # now change your code and save the file
    a.reloadCode('bar') # reloads the file
    a.bar() # now executes the reloaded code
5
répondu Max 2015-08-18 15:32:47

ce que Jason Pratt a posté est correct.

>>> class Test(object):
...   def a(self):
...     pass
... 
>>> def b(self):
...   pass
... 
>>> Test.b = b
>>> type(b)
<type 'function'>
>>> type(Test.a)
<type 'instancemethod'>
>>> type(Test.b)
<type 'instancemethod'>

comme vous pouvez le voir, Python ne considère pas b() différent de a(). En Python toutes les méthodes sont juste des variables qui se trouvent être des fonctions.

3
répondu Acuminate 2008-08-22 14:40:21

si cela peut être d'une quelconque aide, j'ai récemment publié une bibliothèque Python nommé Gorilla pour rendre le processus de correction de singe plus pratique.

utilisant une fonction needle() pour corriger un module nommé guineapig va comme suit:

import gorilla
import guineapig
@gorilla.patch(guineapig)
def needle():
    print("awesome")

mais il s'occupe également de cas d'utilisation plus intéressants comme indiqué dans la FAQ de la documentation .

le code est disponible sur GitHub .

3
répondu ChristopherC 2014-07-15 02:12:30

cette question a été ouverte il y a des années, mais il y a un moyen facile de simuler la liaison d'une fonction à une instance de classe en utilisant des décorateurs:

def binder (function, instance):
  copy_of_function = type (function) (function.func_code, {})
  copy_of_function.__bind_to__ = instance
  def bound_function (*args, **kwargs):
    return copy_of_function (copy_of_function.__bind_to__, *args, **kwargs)
  return bound_function


class SupaClass (object):
  def __init__ (self):
    self.supaAttribute = 42


def new_method (self):
  print self.supaAttribute


supaInstance = SupaClass ()
supaInstance.supMethod = binder (new_method, supaInstance)

otherInstance = SupaClass ()
otherInstance.supaAttribute = 72
otherInstance.supMethod = binder (new_method, otherInstance)

otherInstance.supMethod ()
supaInstance.supMethod ()

là, quand vous passez la fonction et l'instance au Décorateur de classeur, il va créer une nouvelle fonction, avec le même objet de code que le premier. Ensuite, l'instance de la classe est stockée dans un attribut de la fonction nouvellement créée. Le décorateur renvoie une (troisième) fonction appelant automatiquement la fonction copiée, donnant l'instance comme premier paramètre.



En conclusion, vous obtenez une fonction simulant sa liaison à l'instance de classe. Laisser la fonction originale inchangée.

2
répondu lain 2015-12-21 21:39:00

je trouve étrange que personne n'ait mentionné que toutes les méthodes énumérées ci-dessus créent une référence de cycle entre la méthode ajoutée et l'instance, provoquant l'objet à être persistante jusqu'à la collecte des ordures. Il y avait une vieille astuce pour ajouter un descripteur en étendant la classe de l'objet:

def addmethod(obj, name, func):
    klass = obj.__class__
    subclass = type(klass.__name__, (klass,), {})
    setattr(subclass, name, func)
    obj.__class__ = subclass
2
répondu Yu Feng 2017-04-30 04:57:29
from types import MethodType

def method(self):
   print 'hi!'


setattr( targetObj, method.__name__, MethodType(method, targetObj, type(method)) )

avec ceci, vous pouvez utiliser le pointeur automatique

1
répondu Arturo Morales Rangel 2017-07-27 04:21:24