Pergunta sobre metaprogramming, python – Substituindo métodos especiais em uma instância

21

Espero que alguém possa responder isso que tenha um bom entendimento profundo do Python :)

Considere o seguinte código:

<code>>>> class A(object):
...     pass
...
>>> def __repr__(self):
...     return "A"
...
>>> from types import MethodType
>>> a = A()
>>> a
<__main__.A object at 0x00AC6990>
>>> repr(a)
'<__main__.A object at 0x00AC6990>'
>>> setattr(a, "__repr__", MethodType(__repr__, a, a.__class__))
>>> a
<__main__.A object at 0x00AC6990>
>>> repr(a)
'<__main__.A object at 0x00AC6990>'
>>>
</code>

Observe como repr (a) não produz o resultado esperado de "A"? Eu quero saber porque este é o caso e se há uma maneira de conseguir isso ...

Eu contraste, o seguinte exemplo funciona no entanto (Talvez porque não estamos tentando substituir um método especial?):

<code>>>> class A(object):
...     def foo(self):
...             return "foo"
...
>>> def bar(self):
...     return "bar"
...
>>> from types import MethodType
>>> a = A()
>>> a.foo()
'foo'
>>> setattr(a, "foo", MethodType(bar, a, a.__class__))
>>> a.foo()
'bar'
>>>
</code>
Não consegui descrever o que estou querendo fazer em detalhes. Desculpa. Mas resumindo, estou tentando modelar um protótipo de modelo OO onde eu posso fazer operadores como: World = Object (). Clone (). Mixin (World); Onde World é uma classe com uma coleção de métodos que substituem / substituem um na instância Object.clone (). James Mills
Para classes de estilo novo, métodos especiais são pesquisados ​​na classe e não na instância. Veja minha resposta abaixo para mais detalhes e para algumas soluções alternativas. Raymond Hettinger
Você provavelmente terá uma resposta muito mais esclarecedora se disser o que está tentando alcançar. mattmanser

Sua resposta

4   a resposta
3

A razão para isso é métodos especiais (__x__()) são definidos para a classe, não a instância.

Isso faz sentido quando você pensa__new__() - seria impossível chamar isso em uma instância, pois a instância não existe quando é chamada.

Então você pode fazer isso na classe como um todo se você quiser:

<code>>>> A.__repr__ = __repr__
>>> a
A
</code>

Ou em uma instância individual, comona resposta de kindall. (Note que há muita semelhança aqui, mas eu pensei que meus exemplos foram adicionados o suficiente para postar isso também).

4

Para novas classes de estilo, o Python usa uma pesquisa de método especial que ignora instâncias. Aqui um trecho dea fonte:

<code>  1164 /* Internal routines to do a method lookup in the type
  1165    without looking in the instance dictionary
  1166    (so we can't use PyObject_GetAttr) but still binding
  1167    it to the instance.  The arguments are the object,
  1168    the method name as a C string, and the address of a
  1169    static variable used to cache the interned Python string.
  1170 
  1171    Two variants:
  1172 
  1173    - lookup_maybe() returns NULL without raising an exception
  1174      when the _PyType_Lookup() call fails;
  1175 
  1176    - lookup_method() always raises an exception upon errors.
  1177 
  1178    - _PyObject_LookupSpecial() exported for the benefit of other places.
  1179 */
</code>

Você pode alterar para uma classe de estilo antigo (não herdarobjeto) ou você pode adicionar métodos de dispatcher à classe (métodos que encaminham pesquisas para a instância). Para um exemplo de métodos de dispatcher de instância, veja a receita emhttp://code.activestate.com/recipes/578091

AFAIK se eu fosse mudar para usar classes de estilo antigo, isso não funcionaria para o Python 3? James Mills
20

Python não chama os métodos especiais, aqueles com nome rodeado por__ na instância, mas apenas na classe, aparentemente para melhorar o desempenho. Então não há como anular__repr__() diretamente em uma instância e fazê-la funcionar. Em vez disso, você precisa fazer algo assim:

<code>class A(object):
    def __repr__(self):
        return self._repr()
    def _repr(self):
        return object.__repr__(self)
</code>

Agora você pode substituir__repr__() em uma instância, substituindo_repr().

Não é principalmente para melhorar o desempenho. Como dizem os médicos, "A lógica por trás desse comportamento está em vários métodos especiais, como__hash__() e__repr__() que são implementados por todos os objetos, incluindo objetos de tipo. Se a pesquisa implícita desses métodos usasse o processo de pesquisa convencional, eles falhariam quando invocados no próprio objeto de tipo ... "(No entanto, a justificativa para__getattribute__ Além disso sendo pulávelé desempenho.) abarnert
Isto énão sobre desempenho. Métodos especiais são pesquisados ​​no tipo para garantir que tanto uma instância quanto o tipo possam suportar a operação especial. Por exemplo,objetos de classe são hashable. Eles TEM um__hash__ método. Se a classe implementar um__hash__ método para tornar as instâncias hashable, então esse método entraria em conflito sehash(ClassObject) procuraria o método diretamente na classe (que é em si uma instância de um metatipo). Martijn Pieters
Vejoisto. As regras são ligeiramente relaxadas em classes antigas, mas você deve usar classes de estilo novo, e no Python 3 elas são o único tipo de qualquer maneira. kindall
Além disso, "o Python não chama os métodos especiais ..." não é verdade. Python nãogarantia para chamá-los. O CPython tem uma lista específica de casos em que pula a pesquisa normal, mas essa lista é específica da implementação e não está documentada, portanto você não deve confiar nela. Métodos especiais podem ou não ser pesquisados ​​na instância, portanto, verifique se seu código funciona de qualquer maneira. abarnert
"Python não chama os métodos especiais, aqueles com nome cercado por __ na instância, mas apenas na classe, aparentemente para melhorar o desempenho" <- Você tem uma referência a isso? James Mills
10

Como explicado emPesquisa de método especial:

Para classes personalizadas, invocações implícitas de métodos especiais só funcionam corretamente se definidas no tipo de um objeto, não no dicionário da instância do objeto ... Além de ignorar quaisquer atributos da instância com o objetivo de correção, a pesquisa de método especial implícita geralmente também ignora a__getattribute__() método até mesmo da metaclasse do objeto

(A parte que eu tirei explica a lógica por trás disso, se você estiver interessado nisso).

O Python não documenta exatamente quando uma implementação deve ou não procurar o método no tipo; tudo o que ele documenta é, na verdade, que as implementações podem ou não olhar para a instância para pesquisas de método especiais, portanto você não deve contar com nenhuma delas.

Como você pode adivinhar nos resultados dos seus testes, na implementação do CPython,__repr__ é uma das funções pesquisadas no tipo.

As coisas são um pouco diferentes em 2.x, principalmente por causa da presença de classes clássicas, mas desde que você esteja apenas criando classes de estilo novo, você pode pensar nelas como as mesmas.

A razão mais comum que as pessoas querem fazer isso é manipular diferentes instâncias de um objeto para fazer coisas diferentes. Você não pode fazer isso com métodos especiais, então… o que você pode fazer? Há uma solução limpa e uma solução hacky.

A solução limpa é implementar um método especial na classe que apenas chama um método regular na instância. Então você pode macular o patch que o método regular em cada instância. Por exemplo:

<code>class C(object):
    def __repr__(self):
        return getattr(self, '_repr')()
    def _repr(self):
        return 'Boring: {}'.format(object.__repr__(self))

c = C()
def c_repr(self):
    return "It's-a me, c_repr: {}".format(object.__repr__(self))
c._repr = c_repr.__get__(c)
</code>

A solução hacky é construir uma nova subclasse e reclassificar o objeto. Eu suspeito que alguém que realmente tenha uma situação em que esta seja uma boa idéia saiba como implementá-la a partir dessa sentença, e qualquer um que não saiba como fazê-lo não deve estar tentando, então deixarei por isso mesmo.

Eu acho que para trabalhar em 2.x, você pode precisar chamar__get__(c, type(c)), mas não me lembro com certeza, e não tenho um intérprete 2.x de trabalho nesta máquina ... Talvez seja melhor usartypes.MethodType em vez de? abarnert
Existe uma razão pela qual você usagetattr(self, '_repr')() em vez de apenasself._repr()? Nathan

Perguntas relacionadas