Question 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 (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 pourrait-on faire cela?


502
2017-08-04 02:17


origine


Réponses:


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" (comment descriptives) à une instance, et cette instance sera passée 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) ne sont toujours pas liés, vous pouvez donc modifier la définition de la 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 (à condition qu'elles n'aient pas elles-mêmes remplacé l'attribut):

>>> a.fooFighters()
fooFighters

Le problème vient quand vous voulez 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 attachée directement à une instance:

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

Pour lier, nous pouvons utiliser le Fonction MethodType dans le module de 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 à propos de descripteurs et métaclasse  la programmation.


756
2017-08-06 00:33



Module Nouveau est obsolète depuis python 2.6 et supprimé dans la version 3.0, utilisez les types

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

Dans l'exemple ci-dessous, j'ai délibérément retiré la valeur de retour de patch_me() fonction. Je pense que donner une valeur de retour peut faire croire que patch renvoie un nouvel objet, ce qui n'est pas vrai - il modifie le nouvel entrant. 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'>

80
2018-01-21 05:31



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 la 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 le recommande pas. C'est une mauvaise idée. Ne fais pas ça.

Voici quelques raisons:

  • Vous allez ajouter un objet lié à chaque occurrence à 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, et elles cessent alors d'exister lorsqu'elles sont collectées automatiquement. Si vous le faites manuellement, vous aurez une liaison de nom référençant la méthode liée - ce qui empêchera sa récupération de place lors de son 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 non. Les programmeurs ne s'y attendront pas, et vous risquez de violer le règle de moins de surprise.
  • Comme il y a d'autres bonnes raisons de ne pas le faire, vous vous ferez une mauvaise réputation si vous le faites.

Ainsi, je suggère que vous ne fassiez pas ceci à moins que vous ayez une très bonne raison. Il est préférable de définir la bonne méthode dans la définition de classe ou Moins préférablement à singe-patch la classe directement, comme ceci:

Foo.sample_method = sample_method

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

Comment cela peut être fait

Voici un code d'installation. Nous avons besoin d'une définition de classe. Il pourrait être importé, mais cela n'a pas vraiment 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) - utilise la méthode du descripteur, __get__

Les recherches par points 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, importer les types, à partir de laquelle nous aurons le constructeur de la méthode:

import types

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

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

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

et utilisation:

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

Méthode 2: liaison 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

Méthode trois: functools.partial

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

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

Cela est logique 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 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 fonctionner la fonction non liée en passant explicitement l'instance (ou n'importe quoi, puisque cette méthode n'utilise pas réellement le self argument variable), mais cela ne serait pas cohérent avec la signature attendue d'autres instances (si nous appliquons un patch de singe à cette instance):

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

Conclusion

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


52
2018-01-22 14:20



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 ipython:

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

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

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

Il se trouve que m () est juste une fonction, la référence à laquelle est ajouté UNE dictionnaire de classe - il n'y a pas de magie. Alors pourquoi Un 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 A .__ classe __.__ 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 de savoir pourquoi la dernière ligne est imprimée deux fois, mais il reste clair ce qui se passe là-bas.

Maintenant, ce que fait par défaut __getattribute__, c'est qu'il vérifie si l'attribut est un soi-disant descripteur ou non, c'est-à-dire si elle implémente une méthode __get__ spéciale. 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 notre UNE classe, c'est 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 descripteur, si elles sont appelées au nom d'un objet, elles se lient à cet objet dans leur méthode __get__.

Ok, 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

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

Et si vous voulez ajouter une méthode à un seul objet, alors vous devez émuler la machine vous-même, 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'>

31
2017-08-04 02:31



Dans Python, le patch singe fonctionne généralement en écrasant une signature de classe ou de fonction avec la vôtre. Voici un exemple de la Zope Wiki:

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

Ce code va écraser / créer une méthode appelée parler sur la classe. Dans Jeff Atwood post récent sur la correction de singe. Il montre un exemple en C # 3.0 qui est le langage actuel que j'utilise pour le travail.


15
2018-04-26 15:47



Il existe au moins deux manières 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 - invocation de descripteurs
Descriptor HowTo Guide - invocation de descripteurs


9
2017-07-21 12:55



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

Ceci est une chaîne d'instance

Processus terminé avec le code de sortie 0


7
2017-08-07 11:30



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
2018-03-09 15:07



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

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

6
2018-01-28 00:12



Consolider les réponses de Jason Pratt et de la communauté wiki, en examinant les résultats des différentes méthodes de liaison:

Notamment comment ajouter la fonction de liaison en tant que méthode de classe travaux, 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 externe ADDMETHOD, car elle me permet également d’attribuer dynamiquement de nouveaux noms de méthodes à 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>>

5
2017-08-25 21:56



Vous devriez vraiment regarder les gars le fruit interdit, c'est une librairie python qui supporte le patch de singe TOUTES les classes python, même les chaînes.


5
2017-08-18 15:32