Pergunta

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

Entendo que nem sempre é bom fazer isso.Mas como alguém pode fazer isso?

Foi útil?

Solução

Em Python, há uma diferença entre funções e 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>>
>>>

Os métodos vinculados foram "vinculados" (como são descritivos) a uma instância, e essa instância será passada como o primeiro argumento sempre que o método for chamado.

Os chamáveis ​​que são atributos de uma classe (em oposição a uma instância) ainda não estão vinculados, portanto, você pode modificar a definição da classe sempre que desejar:

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

As instâncias definidas anteriormente também são atualizadas (desde que não tenham substituído o atributo):

>>> a.fooFighters()
fooFighters

O problema surge quando você deseja anexar um método a 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 é vinculada automaticamente quando é anexada diretamente a uma instância:

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

Para ligá-lo, podemos usar o Função MethodType no módulo de 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 afetadas:

>>> 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 metaclasse programação.

Outras dicas

Módulo novo está obsoleto desde python 2.6 e removido em 3.0, use tipos

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

No exemplo abaixo, removi deliberadamente o valor de retorno de patch_me() função.Eu acho que dar valor de retorno pode fazer acreditar que patch retorna um novo objeto, o que não é verdade - ele modifica o objeto recebido.Provavelmente isso pode facilitar um uso mais disciplinado 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'>

Prefácio – uma nota sobre compatibilidade:outras respostas podem funcionar apenas no Python 2 - esta resposta deve funcionar perfeitamente no Python 2 e 3.Se estiver escrevendo apenas Python 3, você pode deixar de fora a herança explícita de object, mas caso contrário o código deve permanecer o mesmo.

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

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

Entendo que nem sempre é uma boa decisão fazer isso. Mas, como alguém pode fazer isso?

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

Eu não recomendo isso.Esta é uma má ideia.Não faça isso.

Aqui estão alguns motivos:

  • Você adicionará um objeto vinculado a cada instância em que fizer isso.Se você fizer muito isso, provavelmente desperdiçará muita memória.Os métodos vinculados normalmente são criados apenas durante a curta duração de sua chamada e, então, deixam de existir quando o lixo é coletado automaticamente.Se você fizer isso manualmente, terá uma ligação de nome referenciando o método vinculado - o que impedirá sua coleta de lixo durante o uso.
  • Instâncias de objetos de um determinado tipo geralmente possuem seus métodos em todos os objetos desse tipo.Se você adicionar métodos em outro lugar, algumas instâncias terão esses métodos e outras não.Os programadores não esperarão isso e você corre o risco de violar o regra da menor surpresa.
  • Como existem outras boas razões para não fazer isso, você também terá uma má reputação se fizer isso.

Portanto, sugiro que você não faça isso a menos que tenha um bom motivo. É muito melhor definir o método correto na definição da classe ou menos de preferência para fazer o monkey-patch da classe diretamente, assim:

Foo.sample_method = sample_method

Porém, como é instrutivo, vou mostrar algumas maneiras de fazer isso.

Como pode ser feito

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

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

Crie uma instância:

foo = Foo()

Crie um método para adicionar a ele:

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

Método zero (0) - use o método descritor, __get__

Pesquisas pontilhadas em funções chamam o __get__ método da função com a instância, vinculando o objeto ao método e criando assim um "método vinculado".

foo.sample_method = sample_method.__get__(foo)

e agora:

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

Método um - tipos.MethodType

Primeiro, importe os tipos, dos quais obteremos o construtor do método:

import types

Agora adicionamos o método à instância.Para fazer isso, exigimos o construtor MethodType do types módulo (que importamos acima).

A assinatura do 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:ligação lexical

Primeiro, criamos uma função wrapper que vincula o método à 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.parcial

Uma função parcial aplica o(s) primeiro(s) argumento(s) a uma função (e opcionalmente argumentos de palavras-chave) e pode ser chamada posteriormente com os argumentos restantes (e substituindo argumentos de palavras-chave).Por isso:

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

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

Função não vinculada como um atributo de objeto - por que isso não funciona:

Se tentarmos adicionar sample_method da mesma maneira que poderíamos adicioná-lo à classe, ele será desvinculado da instância e não tomará o self implícito como 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)

Podemos fazer a função não vinculada funcionar passando explicitamente a instância (ou qualquer coisa, já que esse método na verdade não usa o self variável de argumento), mas não seria consistente com a assinatura esperada de outras instâncias (se estivermos corrigindo esta instância):

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

Conclusão

Agora você conhece várias maneiras de poderia faça isso, mas falando sério - não faça isso.

Acho que as respostas acima perderam o ponto principal.

Vamos ter uma classe com um método:

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

Agora vamos brincar com isso no ipython:

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

OK então m() de alguma forma se torna um método ilimitado de A.Mas é realmente assim?

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

Acontece que m() é apenas uma função, cuja referência é adicionada a A dicionário de classe - não há mágica.Então por que Sou nos dá um método não vinculado?É porque o ponto não é traduzido para uma simples pesquisa no dicionário.É de fato 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, não sei ao certo por que a última linha é impressa duas vezes, mas ainda está claro o que está acontecendo lá.

Agora, o que o __getattribute__ padrão faz é verificar se o atributo é chamado descritor ou não, ou sejase implementar um método __get__ especial.Se ele implementar esse método, o que será retornado será o resultado da chamada desse método __get__.Voltando à primeira versão do nosso A classe, isso é o que temos:

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

E como as funções Python implementam o protocolo descritor, se forem chamadas em nome de um objeto, elas se vinculam a esse objeto em seu método __get__.

Ok, então como adicionar um método a um objeto existente?Supondo que você não se importe em corrigir a classe, é tão simples quanto:

B.m = m

Então Bm "torna-se" um método não vinculado, graças ao descritor mágico.

E se você quiser adicionar um método apenas a um único objeto, então você mesmo terá que emular o maquinário, usando types.MethodType:

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

Por falar nisso:

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

Em Python, o monkey patching geralmente funciona substituindo uma assinatura de classe ou função pela sua própria.Abaixo está um exemplo do Wiki do Zope:

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

Esse código irá sobrescrever/criar um método chamado speak na classe.Em Jeff Atwood postagem recente sobre patch de macaco.Ele mostra um exemplo em C# 3.0 que é a linguagem atual que uso para trabalho.

Existem pelo menos duas maneiras de anexar um método a 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 prático do descritor - invocando descritores

Você pode usar lambda para vincular um método a 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()

Saída:

This is instance string

O que você está procurando é setattr Eu acredito.Use isto 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>

Como esta pergunta é feita para versões não-Python, aqui está o JavaScript:

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

Consolidando as respostas de Jason Pratt e do wiki da comunidade, com uma olhada nos resultados de diferentes métodos de vinculação:

Observe especialmente como adicionar a função de ligação como um método de classe funciona, mas o escopo de 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, prefiro a rota da função ADDMETHOD externa, pois ela também me permite atribuir dinamicamente novos nomes de métodos dentro de um iterador.

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

Vocês deveriam realmente olhar fruto proibido, é uma biblioteca python que fornece suporte para correção de QUALQUER classe python, até mesmo strings.

Na verdade, este é um complemento à resposta de "Jason Pratt"

Embora a resposta de Jason funcione, ela só funciona se alguém quiser adicionar uma função a uma classe.Não funcionou para mim quando tentei recarregar um método já existente do arquivo de código-fonte .py.

Levei muito tempo para encontrar uma solução alternativa, mas o truque parece simples...1.St Importar o código do arquivo de código -fonte 2. e forçar um recarregar 3.rd use types.functionType (...) para converter o método importado e vinculado a uma função que você também pode transmitir as variáveis ​​globais atuais, como O método recarregado estaria em um espaço de nome diferente 4.th agora você pode continuar como sugerido por "Jason Pratt" usando os tipos.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

O que Jason Pratt postou 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 de a().Em Python, todos os métodos são apenas variáveis ​​que são funções.

Se puder ajudar, lancei recentemente uma biblioteca Python chamada Gorilla para tornar o processo de patch de macaco mais conveniente.

Usando 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 no Perguntas frequentes de documentação.

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

Esta questão foi aberta anos atrás, mas existe uma maneira fácil de simular a ligação de uma função a uma instância de 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á, ao passar a função e a instância para o decorador do fichário, ele criará uma nova função, com o mesmo objeto de código da primeira.Então, a instância dada da classe é armazenada em um atributo da função recém-criada.O decorador retorna uma (terceira) função chamando automaticamente a função copiada, fornecendo a instância como primeiro parâmetro.

Concluindo, você obtém uma função simulando sua ligação à instância da classe.Deixando a função original inalterada.

Acho estranho que ninguém tenha mencionado que todos os métodos listados acima criam uma referência de ciclo entre o método adicionado e a instância, fazendo com que o objeto seja persistente até a coleta de lixo.Havia um truque antigo para 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
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 automático

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top