Substituir um método no nível da instância

87

Existe uma maneira em Python de substituir um método de classe no nível da instância? Por exemplo:

class Dog:
    def bark(self):
        print "WOOF"

boby = Dog()
boby.bark() # WOOF
# METHOD OVERRIDE
boby.bark() # WoOoOoF!!
pistacchio
fonte

Respostas:

11

Por favor, não faça isso conforme mostrado. Seu código se torna ilegível quando você faz um monkeypatch em uma instância para ser diferente da classe.

Você não pode depurar código monkeypatched.

Quando você encontrar um bug em bobye print type(boby), verá que (a) é um cão, mas (b) por alguma razão obscura ele não late corretamente. Isso é um pesadelo. Não faça isso.

Em vez disso, faça isso.

class Dog:
    def bark(self):
        print "WOOF"

class BobyDog( Dog ):
    def bark( self ):
        print "WoOoOoF!!"

otherDog= Dog()
otherDog.bark() # WOOF

boby = BobyDog()
boby.bark() # WoOoOoF!!
S.Lott
fonte
9
@arivero: Achei que o "Por favor, não faça isso conforme mostrado" deixou isso perfeitamente claro. Que outras palavras você gostaria de ver para deixar mais claro que isso não é uma resposta à pergunta que foi feita, mas sim um conselho sobre por que é uma má ideia?
S.Lott
45
Não discordo do conselho, nem do OP ao que parece. Mas presumo que as pessoas tenham motivos para perguntar. Ou mesmo que o OP não tenha, outros futuros visitantes poderiam. Então, IMHO, uma resposta mais uma reprimenda é melhor do que apenas uma reprimenda.
arivero de
13
@arivero: Isso não respondeu à minha pergunta.
S.Lott
9
@ S.Lott, acho que você deve vincular o "mostrado" à resposta real, eu realmente não tenho nenhum problema em ser a resposta aceita, mas há razões pelas quais você precisa fazer o monkey patch em algumas circunstâncias e de minha leitura superficial Considerei "conforme mostrado" para significar o que você estava mostrando, em vez de uma resposta diferente.
Daniel Chatfield
4
A criação de subclasses é um contrato muito mais forte do que o patching de um método. Quando possível, contratos fortes evitam comportamento inesperado. Mas em alguns casos, um contrato mais flexível é desejável. Nessas situações, eu preferiria usar um callback - em vez de monkey-patching - porque, ao permitir que algum comportamento seja personalizado, o contrato de classe permanece inalterado. Sua resposta, embora um conselho prático, é muito pobre para esta pergunta e seu estilo de codificação é inconsistente.
Aaron3468
166

Sim é possivel:

class Dog:
    def bark(self):
        print "Woof"

def new_bark(self):
    print "Woof Woof"

foo = Dog()

funcType = type(Dog.bark)

# "Woof"
foo.bark()

# replace bark with new_bark for this object only
foo.bark = funcType(new_bark, foo, Dog)

foo.bark()
# "Woof Woof"
codelógica
fonte
1
Um pequeno comentário, quando você faz funcType (new_bark, foo, Dog) é adicionar o nome == bark e o método de instância Dog.new_bark em foo .__ dict__ certo? Então, quando você chamar novamente, primeiro procure no dicionário de instância e chame por isso,
Tiago
11
Explique o que isso faz, especialmente o que funcTypefaz e por que é necessário.
Aleksandr Dubinsky
3
Acho que isso pode ser um pouco mais simples e explícito usando funcType = types.MethodType(após a importação types) em vez de funcType = type(Dog.bark).
Elias Zamaria
13
Acho que isso não funciona com Python 3. Estou recebendo o erro "TypeError: function () o argumento 1 deve ser código, não função". Alguma sugestão para Python 3?
Sait
2
Você também pode chamar __get__a função para vinculá-la à instância.
Mad Physicist de
37

Você precisa usar o MethodType do typesmódulo. O objetivo MethodTypeé sobrescrever métodos de nível de instância (para que selfpossam estar disponíveis em métodos sobrescritos).

Veja o exemplo abaixo.

import types

class Dog:
    def bark(self):
        print "WOOF"

boby = Dog()
boby.bark() # WOOF

def _bark(self):
    print "WoOoOoF!!"

boby.bark = types.MethodType(_bark, boby)

boby.bark() # WoOoOoF!!
Harshal Dhumal
fonte
31

Para explicar a excelente resposta de @codelogic, proponho uma abordagem mais explícita. Esta é a mesma técnica que o .operador usa para vincular um método de classe quando você o acessa como um atributo de instância, exceto que seu método será na verdade uma função definida fora de uma classe.

Trabalhando com o código do @codelogic, a única diferença é como o método é vinculado. Estou usando o fato de que funções e métodos não são descritores de dados em Python e invocando o __get__método. Observe particularmente que o original e a substituição têm assinaturas idênticas, o que significa que você pode escrever a substituição como um método de classe completa, acessando todos os atributos de instância via self.

classe Cachorro:
    def bark (próprio):
        imprimir "Woof"

def new_bark (self):
    imprimir "Woof Woof"

foo = Cachorro ()

# "Woof"
foo.bark ()

# substituir bark por new_bark apenas para este objeto
foo.bark = new_bark .__ get __ (foo, Cachorro)

foo.bark ()
# "Woof Woof"

Ao atribuir o método vinculado a um atributo de instância, você criou uma simulação quase completa de substituição de um método. Um recurso útil que está faltando é o acesso à versão sem argumentos do super, já que você não está em uma definição de classe. Outra coisa é que o __name__atributo do seu método vinculado não terá o nome da função que está substituindo, como faria na definição da classe, mas você ainda pode configurá-lo manualmente. A terceira diferença é que seu método ligado manualmente é uma referência de atributo simples que por acaso é uma função. O .operador não faz nada além de buscar essa referência. Ao invocar um método regular de uma instância, por outro lado, o processo de vinculação cria um novo método vinculado a cada vez.

A única razão pela qual isso funciona, a propósito, é que os atributos da instância substituem os descritores que não são de dados . Os descritores de dados têm __set__métodos, métodos que (felizmente para você) não têm. Os descritores de dados na classe realmente têm prioridade sobre quaisquer atributos de instância. É por isso que você pode atribuir a uma propriedade: seu __set__método é invocado quando você tenta fazer uma atribuição. Pessoalmente, gosto de dar um passo adiante e ocultar o valor real do atributo subjacente na instância __dict__, onde é inacessível por meios normais exatamente porque a propriedade o obscurece.

Você também deve ter em mente que isso é inútil para métodos mágicos (sublinhado duplo) . É claro que os métodos mágicos podem ser substituídos dessa forma, mas as operações que os usam olham apenas para o tipo. Por exemplo, você pode definir __contains__algo especial em sua instância, mas chamar x in instancedesconsideraria isso e usaria em seu type(instance).__contains__(instance, x)lugar. Isso se aplica a todos os métodos mágicos especificados no modelo de dados Python .

Físico Louco
fonte
1
Esta deve ser a resposta aceita: está limpo e funciona em Python 3.
BlenderBender
@BlenderBender. Agradeço seu apoio
Mad Physicist
Qual é a diferença entre esta resposta e aquela acima da sua de @Harshal Dhumai?
1313e
1
@ 1313. Funcionalmente, não deve haver muita diferença. Suspeito que a resposta acima pode aceitar uma gama maior de chamáveis ​​como entrada, mas sem ler a documentação e brincar, não tenho certeza.
Mad Physicist
Documentos do Python 2 para isso: docs.python.org/2/howto/descriptor.html#functions-and-methods . Então, teoricamente, é o mesmo que a resposta de @Harshal Dhumai.
Rockallite
26
class Dog:
    def bark(self):
        print "WOOF"

boby = Dog()
boby.bark() # WOOF

# METHOD OVERRIDE
def new_bark():
    print "WoOoOoF!!"
boby.bark = new_bark

boby.bark() # WoOoOoF!!

Você pode usar a bobyvariável dentro da função se precisar. Desde que você está substituindo o método apenas para este objeto uma instância, desta forma é mais simples e tem exatamente o mesmo efeito que usar self.

nosklo
fonte
1
IMHO usando a assinatura original aumenta a legibilidade, especialmente se a função for definida em outro lugar no código, não perto da instância. A exceção seria o caso em que o método de substituição também é usado independentemente como uma função. Claro, neste exemplo simples, não importa.
codelogic
1
Não entendo por que essa resposta não é aceita. É chamado patchinge esta é a maneira correta de fazer (por exemplo, boby = Dog()e boby.bark = new_bark). É extremamente útil em testes de unidade para controles. Para obter mais explicações, consulte tryolabs.com/blog/2013/07/05/run-time-method-patching-python (exemplos) - não, não sou afiliado ao site ou autor vinculado.
Geoff de
5
O método new_bark não tem acesso a self (instância), então não há como o usuário acessar as propriedades da instância em new_bark. Em vez disso, é necessário usar o MethodType do módulo de tipos (veja minha resposta abaixo).
Harshal Dhumal de
3

Já que ninguém está mencionando functools.partialaqui:

from functools import partial

class Dog:
    name = "aaa"
    def bark(self):
        print("WOOF")

boby = Dog()
boby.bark() # WOOF

def _bark(self):
    print("WoOoOoF!!")

boby.bark = partial(_bark, boby)
boby.bark() # WoOoOoF!!
user1285245
fonte
1

Como as funções são objetos de primeira classe em Python, você pode passá-las ao inicializar seu objeto de classe ou substituí-lo a qualquer momento para uma determinada instância de classe:

class Dog:
    def __init__(self,  barkmethod=None):
        self.bark=self.barkp
        if barkmethod:
           self.bark=barkmethod
    def barkp(self):
        print "woof"

d=Dog()
print "calling original bark"
d.bark()

def barknew():
    print "wooOOOoof"

d1=Dog(barknew)
print "calling the new bark"
d1.bark()

def barknew1():
    print "nowoof"

d1.bark=barknew1
print "calling another new"
d1.bark()

e os resultados são

calling original bark
woof
calling the new bark
wooOOOoof
calling another new
nowoof
JV.
fonte
-4

Embora eu tenha gostado da ideia de herança de S. Lott e concordado com o 'tipo (a)', mas como as funções também têm atributos acessíveis, acho que pode ser gerenciado desta forma:

class Dog:
    def __init__(self, barkmethod=None):
        self.bark=self.barkp
        if barkmethod:
           self.bark=barkmethod
    def barkp(self):
        """original bark"""
        print "woof"

d=Dog()
print "calling original bark"
d.bark()
print "that was %s\n" % d.bark.__doc__

def barknew():
    """a new type of bark"""
    print "wooOOOoof"

d1=Dog(barknew)
print "calling the new bark"
d1.bark()
print "that was %s\n" % d1.bark.__doc__

def barknew1():
    """another type of new bark"""
    print "nowoof"

d1.bark=barknew1
print "another new"
d1.bark()
print "that was %s\n" % d1.bark.__doc__

e a saída é:

calling original bark
woof
that was original bark

calling the new bark
wooOOOoof
that was a new type of bark

another new
nowoof
that was another type of new bark
JV.
fonte
2
Se for necessário "administrar", então - para mim - algo está errado. Especialmente quando há um recurso de linguagem de primeira classe que já faz o trabalho.
S.Lott
-4

Caro, isso não é anulação, você está apenas chamando a mesma função duas vezes com o objeto. Basicamente, a substituição está relacionada a mais de uma classe. quando o mesmo método de assinatura existe em classes diferentes, qual função você está chamando, decide o objeto que a chama. A substituição é possível em python quando você faz com que mais de uma classe escreva as mesmas funções e mais uma coisa para compartilhar que a sobrecarga não é permitida em python

Nagimsit
fonte
-4

Achei que esta é a resposta mais precisa para a pergunta original

https://stackoverflow.com/a/10829381/7640677

import a

def _new_print_message(message):
    print "NEW:", message

a.print_message = _new_print_message

import b
b.execute()
Ruvalcaba
fonte