Adicionando um método para uma instância de objeto existente

votos
488

Eu li que é possível adicionar um método para um objeto existente (ou seja, não na definição de classe) em Python.

Eu entendo que nem sempre é bom para fazê-lo. Mas como se pode fazer isso?

Publicado 04/08/2008 em 03:17
fonte usuário
Em outras línguas...                            


18 respostas

votos
736

Em Python, existe uma diferença entre as funções e os métodos vinculados.

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

métodos têm sido ligados "ligado" (como descritiva) a um exemplo, e que instância irá ser transmitido como o primeiro argumento, sempre que o método é chamado.

Callables que são atributos de uma classe (em oposição a uma instância) ainda estão soltos, embora, assim você pode modificar a definição de classe sempre que quiser:

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

casos previamente definidos são atualizados bem (desde que eles não tenham substituído o atributo em si):

>>> a.fooFighters()
fooFighters

O problema surge quando você deseja anexar um método para uma única instância:

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

A função não é ligada automaticamente quando ele está ligado directamente a um exemplo:

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

Para ligá-la, podemos usar a função MethodType no módulo tipos :

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

Desta vez, outras instâncias da classe não foram afetados:

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

Mais informações podem ser encontradas lendo sobre descritores e metaclass programação .

Respondeu 06/08/2008 em 01:33
fonte usuário

votos
80

Módulo de novo é preterido desde pitão 2,6 e 3,0, removido em uso tipos

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

No exemplo abaixo eu valor de retorno de deliberadamente removido patch_me()função. Eu acho que dar valor de retorno pode fazer crer que o patch retorna um novo objeto, o que não é verdade - ele modifica a entrada. Provavelmente, isso pode facilitar uma utilização mais disciplinada do 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'>
Respondeu 06/06/2009 em 06:31
fonte usuário

votos
47

Adicionando um método para uma instância de objeto existente

Eu li que é possível adicionar um método para um objeto existente (por exemplo, não na definição de classe) em Python.

Eu entendo que nem sempre é uma boa decisão para fazer isso. Mas, como se pode fazer isso?

Sim, é possível - mas não é recomendado

Eu não recomendo este. Esta é uma má idéia. Não fazê-lo.

Aqui está um par de razões:

  • Você vai adicionar um objeto obrigado a todos os casos que você faça isso. Se você fizer isso muito, você provavelmente vai perder uma grande quantidade de memória. métodos vinculados são normalmente apenas criado para a curta duração da sua chamada, e eles, em seguida, deixam de existir quando automaticamente lixo coletado. Se você fazer isso manualmente, você terá um nome de ligação referenciando o método ligado - o que impedirá a sua coleta de lixo no uso.
  • Instâncias de objetos de um determinado tipo têm geralmente seus métodos em todos os objetos desse tipo. Se você adicionar métodos em outros lugares, alguns casos terá esses métodos e outros não. Os programadores não vai esperar isso, e corre o risco de violar a regra da menor surpresa .
  • Uma vez que existem outras muito boas razões para não fazer isso, você vai além disso, dar-se uma má reputação se você fizer isso.

Assim, sugiro que você não fazer isso a menos que tenha uma razão muito boa. É muito melhor para definir o método correcto na definição de classe ou menos preferencialmente para-macaco remendo da classe directamente, como este:

Foo.sample_method = sample_method

Desde que é instrutivo, no entanto, eu vou mostrar-lhe algumas maneiras de fazer isso.

Como pode ser feito

Aqui está um código de configuração. Precisamos de uma definição de classe. Pode ser importado, mas realmente não importa.

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

Criar uma instância:

foo = Foo()

Crie um método para adicionar a ele:

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

Método nada (0) - usar o método descritor, __get__

Pesquisas ponteadas nos funções chamar o __get__método da função com o exemplo, a ligação do objecto ao método e criando, assim, um "método ligado."

foo.sample_method = sample_method.__get__(foo)

e agora:

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

Método um - types.MethodType

Primeiro, tipos de importação, a partir do qual teremos o construtor método:

import types

Agora vamos adicionar o método para a instância. Para fazer isso, exigimos o construtor MethodType do typesmódulo (que nós importados acima).

A assinatura argumento para types.MethodType é (function, instance, class):

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

e uso:

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

Método dois: lexical ligao

Primeiro, criamos uma função wrapper que liga o método para a instância:

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

uso:

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

Método três: functools.partial

Uma função parcial aplica-se o primeiro argumento (s) para uma funo (e, opcionalmente, os argumentos de palavra-chave), e mais tarde pode ser chamada com os argumentos restantes (e argumentos chave imperativas). Portanto:

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

Isso faz sentido quando se considera que os métodos vinculados são funções parciais de instância.

função Unbound como um atributo de objeto - por que isso não funciona:

Se tentar adicionar o sample_method da mesma forma como podemos adicioná-lo para a classe, é desacoplado da instância, e não faça o auto implícita como o primeiro argumento.

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

Nós podemos fazer o trabalho função não ligado por passar explicitamente a instância (ou qualquer coisa, uma vez que este método não realmente usar a selfvariável argumento), mas não seria consistente com a assinatura esperado de outras instâncias (se estamos monkey-patching neste exemplo):

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

Conclusão

Você já sabe várias maneiras que você poderia fazer isso, mas com toda a seriedade - não faça isso.

Respondeu 21/01/2015 em 05:31
fonte usuário

votos
30

Eu acho que as respostas acima perdeu o ponto chave.

Vamos ter uma classe com um método:

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

Agora, vamos jogar com ele em ipython:

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

Ok, então m () de alguma forma torna-se um método não ligada de um . Mas é realmente assim?

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

Acontece que m () é apenas uma função, a referência a que é adicionado a um dicionário de classe - não há mágica. Então por Am nos dá um método não ligado? É porque o ponto não é traduzida para uma pesquisa de dicionário simples. É de facto uma chamada 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

Agora, eu não tenho certeza para fora do topo da minha cabeça porque a última linha é impresso duas vezes, mas ainda está claro o que está acontecendo lá.

Agora, o que o __getattribute__ padrão faz é que ele verifica se o atributo é um chamado descritor ou não, ou seja, se ele implementa um método __get__ especial. Se ele implementa esse método, então o que é retornado é o resultado de chamar esse método __get__. Voltando à primeira versão do nosso Uma classe, isto é o que temos:

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

E porque as funções do Python implementar o protocolo descritor, se eles são chamados em nome de um objeto, eles ligam-se a esse objeto em seu método __get__.

Ok, então como adicionar um método para um objeto existente? Supondo que você não se importa de classe remendar, é tão simples como:

B.m = m

Então Bm "se torna" um método não ligado, graças à magia descritor.

E se você quiser adicionar um método apenas para um único objeto, então você tem que imitar a maquinaria mesmo, usando types.MethodType:

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

A propósito:

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'>
Respondeu 22/01/2012 em 15:20
fonte usuário

votos
16

Em Python macaco patching geralmente funciona, substituindo a assinatura de classe ou funções com o seu próprio. Abaixo está um exemplo do Zope Wiki :

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

Esse código irá substituir / criar um método chamado falar sobre a classe. Em de Jeff Atwood recente post sobre patching macaco . Ele mostra um exemplo em C # 3.0, que é a linguagem atual eu uso para o trabalho.

Respondeu 04/08/2008 em 03:31
fonte usuário

votos
9

Há pelo menos duas maneiras para anexar um método para uma instância sem 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>>

Links úteis:
Modelo de dados - invocando descritores
Guia descritor HowTo - descritores invocando

Respondeu 26/04/2013 em 16:47
fonte usuário

votos
7

Você pode usar lambda para vincular um método para uma instância:

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

Esta é uma string instância

Processo terminou com código de saída 0

Respondeu 21/07/2014 em 13:55
fonte usuário

votos
6

Uma vez que esta pergunta para as versões não-Python, aqui está JavaScript:

a.methodname = function () { console.log("Yay, a new method!") }
Respondeu 09/03/2012 em 16:07
fonte usuário

votos
6

O que você está procurando setattrEu acredito. Use para definir um atributo em um objeto.

>>> 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>
Respondeu 07/08/2008 em 12:30
fonte usuário

votos
5

Este é realmente um complemento à resposta de "Jason Pratt"

Embora Jasons responder obras, ele só funciona se alguém quiser adicionar uma função a uma classe. Não funcionou para mim quando eu tentava recarregar um método já existente a partir do arquivo de código fonte .py.

Levei para as idades para encontrar uma solução, mas o truque parece simples ... 1.st importar o código do arquivo de código-fonte 2.ª forçar uma types.FunctionType uso de recarga 3.rd (...) para converter o importados e método vinculado a uma função que também pode passar sobre as variáveis ​​globais atuais, como o método recarregado estaria em 4.th namespace diferente agora você pode continuar como sugerido por "Jason Pratt" usando o types.MethodType (... )

Exemplo:

# 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
Respondeu 18/08/2015 em 15:32
fonte usuário

votos
5

Vocês realmente deve olhar fruto proibido , é uma biblioteca python que fornece suporte para macaco patching qualquer classe python, até mesmo cordas.

Respondeu 25/08/2013 em 22:56
fonte usuário

votos
5

Consolidando Jason Pratt das ea comunidade wiki respostas, com um olhar para os resultados de diferentes métodos de ligação:

Especialmente notar como adicionar a função de ligação como um método de classe funciona , mas o escopo referência está incorreto.

#!/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>>

Pessoalmente, eu prefiro a rota função addMethod externo, pois permite-me para atribuir dinamicamente novos nomes de métodos dentro de um iterador também.

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>>
Respondeu 28/01/2012 em 01:12
fonte usuário

votos
4

O que Jason Pratt publicado está correto.

>>> 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'>

Como você pode ver, Python não considera b () diferente do que a (). Em Python todos os métodos são apenas variáveis ​​que venham a ser funções.

Respondeu 22/08/2008 em 15:40
fonte usuário

votos
3

Se ele pode ser de alguma ajuda, eu lançou recentemente uma biblioteca Python chamado Gorilla para tornar o processo de macaco patching mais conveniente.

Utilizando uma função needle()para corrigir um módulo chamado guineapigé o seguinte:

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

Mas também cuida de casos de uso mais interessantes, como mostrado na FAQ da documentação .

O código está disponível no GitHub .

Respondeu 15/07/2014 em 03:12
fonte usuário

votos
2

Acho estranho que ninguém mencionado que todos os métodos listados acima cria uma referência ciclo entre o método adicionado e o exemplo, fazendo com que o objecto a ser persistente até a recolha de lixo. Havia um velho truque de adicionar um descritor estendendo a classe do objeto:

def addmethod(obj, name, func):
    klass = obj.__class__
    subclass = type(klass.__name__, (klass,), {})
    setattr(subclass, name, func)
    obj.__class__ = subclass
Respondeu 30/04/2017 em 04:57
fonte usuário

votos
2

Esta questão foi aberto há anos, mas hey, há uma maneira fácil para simular a ligação de uma função para uma instância da classe usando decoradores:

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á, quando você passar a função e a instância para o decorador pasta, ele irá criar uma nova função, com o mesmo objeto código como o primeiro. Em seguida, o exemplo dado da classe é armazenada em um atributo da função recém-criada. O decorador retornar uma função (terceiro) chamar automaticamente a função copiados, dando a instância como o primeiro parâmetro.

Em conclusão você receber uma função de simulação é vinculativo para a instância da classe. Deixando a função original inalterada.

Respondeu 21/12/2015 em 21:39
fonte usuário

votos
1
from types import MethodType

def method(self):
   print 'hi!'


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

Com isso, você pode usar o ponteiro auto

Respondeu 27/07/2017 em 04:21
fonte usuário

votos
-8

Eu não sei a sintaxe Python, mas eu sei Ruby pode fazê-lo, e é bastante trivial. Vamos dizer que você deseja adicionar um método para Array que imprime o comprimento para fora padrão:

class Array
  def print_length
    puts length
  end
end

Se você não deseja modificar toda a classe, você pode simplesmente adicionar o método a uma única instância do array, e não outras matrizes terão o método:

array = [1, 2, 3]
def array.print_length
  puts length
end

Apenas estar ciente das questões envolvidas na utilização deste recurso. Jeff Atwood realmente escreveu sobre isso não muito tempo atrás.

Respondeu 04/08/2008 em 03:36
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more