python - Ajout d'une méthode à une instance d'objet existante

Translate

J'ai lu qu'il est possible d'ajouter une méthode à un objet existant (c'est-à-dire pas dans la définition de classe) en Python.

Je comprends que ce n'est pas toujours bon de le faire. Mais comment faire cela?

This question and all comments follow the "Attribution Required."

Toutes les réponses

Translate

En Python, il existe 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» (de manière descriptive) à une instance, et cette instance sera passée comme premier argument à chaque fois que la méthode est appelée.

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

>>> 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 précédemment définies sont également mises à jour (tant qu'elles n'ont pas remplacé l'attribut elles-mêmes):

>>> a.fooFighters()
fooFighters

Le problème survient lorsque vous souhaitez attacher une méthode à une seule instance:

>>> 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 directement attachée à une instance:

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

Pour le lier, nous pouvons utiliser leFonction 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 instances de la classe n'ont pas été affectées:

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

Plus d'informations peuvent être trouvées en lisant surdescripteursetmétaclasse la programmation.

La source
Translate

ModuleNouveauest obsolète depuis python 2.6 et supprimé dans 3.0, utilisezles types

voirhttp://docs.python.org/library/new.html

Dans l'exemple ci-dessous, j'ai délibérément supprimé la valeur de retour depatch_me()fonction. Je pense que donner une valeur de retour peut faire croire que le patch renvoie un nouvel objet, ce qui n'est pas vrai - il modifie celui qui arrive. Cela peut probablement faciliter une utilisation plus disciplinée du monkeypatching.

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'>
La source
Translate

Préface - une note sur la compatibilité: les 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 pouvez omettre l'héritage explicite deobject, mais sinon le code doit rester le même.

Ajout d'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 la définition de classe) en Python.

Je comprends que ce n'est pas toujours une bonne décision de le faire.Mais comment pourrait-on faire cela?

Oui, c'est possible - mais pas recommandé

Je ne recommande pas cela. C'est une mauvaise idée. Ne fais pas ça.

Voici quelques raisons:

  • Vous allez ajouter un objet lié à chaque instance à laquelle vous faites cela. Si vous faites cela beaucoup, vous perdrez probablement beaucoup de mémoire. Les méthodes liées ne sont généralement créées que pour la courte durée de leur appel, puis cessent d'exister lorsqu'elles sont automatiquement récupérées. Si vous faites cela manuellement, vous aurez une liaison de nom faisant référence à la méthode liée - ce qui empêchera son garbage collection lors de l'utilisation.
  • Les instances d'objet d'un type donné ont généralement ses 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 pas. Les programmeurs ne s'y attendront pas et vous risquez de violer lesrègle de la moindre surprise.
  • Comme il existe d'autres très bonnes raisons de ne pas le faire, vous vous donnerez également une mauvaise réputation si vous le faites.

Ainsi, je vous suggère de ne pas faire cela à moins d'avoir une très bonne raison.Il est de loin préférable de définir la méthode correcte dans la définition de classeouMoinsde préférence pour monkey-patcher la classe directement, comme ceci:

Foo.sample_method = sample_method

Comme c'est instructif, cependant, je vais vous montrer quelques moyens de le faire.

Comment cela peut être fait

Voici un code de configuration. Nous avons besoin d'une définition de classe. Il pourrait être importé, mais cela n'a vraiment pas d'importance.

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

Créez une instance:

foo = Foo()

Créez une méthode pour y ajouter:

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

Méthode naught (0) - utilisez la méthode du descripteur,__get__

Les recherches en pointillé sur les fonctions appellent le__get__méthode 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 un - types.MethodType

Tout d'abord, importez les types, à partir desquels 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 dutypesmodule (que nous avons importé ci-dessus).

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

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

et utilisation:

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

Deuxième méthode: la reliure lexicale

Tout d'abord, nous créons une fonction wrapper 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

usage:

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

Troisième méthode: functools.partial

Une fonction partielle applique le ou les premiers arguments à une fonction (et éventuellement des arguments de mot-clé), et peut être appelée ultérieurement avec les arguments restants (et en remplaçant les arguments de mot-clé). Donc:

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

Cela a du sens lorsque vous considérez que les méthodes liées sont des fonctions partielles de l'instance.

Fonction non liée en tant qu'attribut d'objet - pourquoi cela ne fonctionne pas:

Si nous essayons d'ajouter le sample_method de la même manière que nous pourrions l'ajouter à la classe, il n'est pas lié à l'instance et ne prend pas le self 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 fonctionner la fonction indépendante en passant explicitement l'instance (ou quoi que ce soit, puisque cette méthode n'utilise pas réellement leselfargument variable), mais cela ne serait pas cohérent avec la signature attendue des autres instances (si nous corrigeons cette instance):

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

Conclusion

Vous connaissez maintenant plusieurs façons dont vouspourraitfaites ceci, mais sérieusement - ne faites pas ceci.

La source
Translate

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

Ayons une classe avec une méthode:

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

Maintenant, jouons avec en ipython:

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

D'accord alorsm ()devient en quelque sorte une méthode non liée deA. Mais est-ce vraiment comme ça?

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

Il se trouve quem ()est juste une fonction, à laquelle une référence est ajoutée àAdictionnaire de classe - il n'y a pas de magie. Alors pourquoiUn mnous donne une méthode non consolidée? C'est parce que le point n'est pas traduit en une simple recherche de dictionnaire. C'est de facto un appel de A .__ class __.__ 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 sais pas du haut de ma tête pourquoi la dernière ligne est imprimée deux fois, mais ce qui se passe là-bas est toujours clair.

Maintenant, ce que fait le __getattribute__ par défaut, c'est qu'il vérifie si l'attribut est un soi-disantdescripteurou pas, c'est-à-dire s'il implémente une méthode spéciale __get__. S'il implémente cette méthode, alors ce qui est retourné est le résultat de l'appel de cette méthode __get__. Revenons à la première version de notreAclasse, voici ce que nous avons:

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

Et comme les fonctions Python implémentent le protocole de descripteur, si elles sont appelées au nom d'un objet, elles se lient à cet objet dans leur méthode __get__.

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

B.m = m

ensuiteBm"devient" une méthode non liée, grâce à la magie des descripteurs.

Et si vous souhaitez ajouter une méthode à un seul objet, vous devez émuler vous-même la machinerie, en utilisant types.MethodType:

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

Au fait:

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'>
La source
Translate

En Python, la correction des singes fonctionne généralement en écrasant une signature de classe ou de fonctions par la vôtre. Voici un exemple de laZope Wiki:

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

Ce code écrasera / créera une méthode appelée speak sur la classe. Dans Jeff Atwood'sarticle récent sur le patching des singes. Il montre un exemple en C # 3.0 qui est la langue actuelle que j'utilise pour mon travail.

La source
Translate

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()

Production:

This is instance string
La source
Translate

Il existe au moins deux façons d'attacher une méthode à une instance sanstypes.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 - appel de descripteurs
Guide pratique des descripteurs - appel de descripteurs

La source
HS.
Translate

Ce que vous cherchez c'estsetattrJe 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>
La source
Translate

Puisque cette question concernait les versions non Python, voici JavaScript:

a.methodname = function () { console.log("Yay, a new method!") }
La source
Translate

Consolider les réponses de Jason Pratt et du wiki de la communauté, avec un regard sur les résultats des différentes méthodes de liaison:

Notez en particulier comment l'ajout de la fonction de liaison en tant que méthode de classetravaux, 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 également d'attribuer dynamiquement de nouveaux noms de méthode dans un itérateur.

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>>
La source
Max
Translate

Ceci est en fait un complément à la réponse de "Jason Pratt"

Bien que la réponse de Jasons fonctionne, elle ne fonctionne que si l'on veut ajouter une fonction à une classe. Cela n'a pas fonctionné pour moi lorsque j'ai essayé de recharger une méthode déjà existante à partir du fichier de code source .py.

Il m'a fallu des siècles pour trouver une solution de contournement, mais l'astuce semble simple ... 1.st importer le code à partir du fichier de code source 2. et forcer un rechargement 3.rd utiliser des types.FunctionType (...) pour convertir le méthode importée et liée à 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.e 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
La source
Translate

Si cela peut être utile, j'ai récemment publié une bibliothèque Python nommée Gorilla pour rendre le processus de correction des singes plus pratique.

Utiliser une fonctionneedle()pour patcher un module nomméguineapigva comme suit:

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

Mais il prend également en charge des cas d'utilisation plus intéressants, comme indiqué dans leFAQduDocumentation.

Le code est disponible surGitHub.

La source
Translate

Ce que Jason Pratt a publié 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 () comme différent de a (). En Python, toutes les méthodes ne sont que des variables qui se trouvent être des fonctions.

La source
Translate

Cette question a été ouverte il y a des années, mais bon, il existe un moyen simple de simuler la liaison d'une fonction à une instance de classe à l'aide de 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à, lorsque vous passez la fonction et l'instance au décorateur de classeur, il créera une nouvelle fonction, avec le même objet de code que le premier. Ensuite, l'instance donnée 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 d'origine inchangée.

La source
Translate

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, ce qui rend l'objet persistant jusqu'au ramassage des ordures. Il y avait une vieille astuce ajoutant 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
La source
Translate
from types import MethodType

def method(self):
   print 'hi!'


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

Avec cela, vous pouvez utiliser le pointeur automatique

La source