Obtenha a classe que definiu o método

89

Como posso obter a classe que definiu um método em Python?

Eu gostaria que o seguinte exemplo imprimisse " __main__.FooClass":

class FooClass:
    def foo_method(self):
        print "foo"

class BarClass(FooClass):
    pass

bar = BarClass()
print get_class_that_defined_method(bar.foo_method)
Jesse Aldridge
fonte
Qual versão do Python você está usando? Antes do 2.2, você podia usar im_class, mas isso foi alterado para mostrar o tipo do objeto self ligado.
Kathy Van Stone
1
Bom saber. Mas estou usando 2.6.
Jesse Aldridge

Respostas:

70
import inspect

def get_class_that_defined_method(meth):
    for cls in inspect.getmro(meth.im_class):
        if meth.__name__ in cls.__dict__: 
            return cls
    return None
Alex Martelli
fonte
1
Obrigado, demoraria um pouco para descobrir isso sozinho
David
Cuidado, nem todas as classes implementam __dict__! Às vezes __slots__é usado. Provavelmente é melhor usar getattrpara testar se o método está na classe.
Codie CodeMonkey
16
Para Python 3, consulte esta resposta .
Yoel
27
Estou recebendo:'function' object has no attribute 'im_class'
Zitrax
5
No Python 2.7, isso não funciona. Mesmo erro sobre a falta de 'im_class'.
RedX
8

Obrigado Sr2222 por apontar que eu estava perdendo o ponto ...

Aqui está a abordagem corrigida, que é igual à de Alex, mas não exige a importação de nada. No entanto, não acho que seja uma melhoria, a menos que haja uma enorme hierarquia de classes herdadas, já que essa abordagem para assim que a classe definidora é encontrada, em vez de retornar toda a herança como o getmrofaz. Como disse, este é um cenário muito improvável.

def get_class_that_defined_method(method):
    method_name = method.__name__
    if method.__self__:    
        classes = [method.__self__.__class__]
    else:
        #unbound method
        classes = [method.im_class]
    while classes:
        c = classes.pop()
        if method_name in c.__dict__:
            return c
        else:
            classes = list(c.__bases__) + classes
    return None

E o exemplo:

>>> class A(object):
...     def test(self): pass
>>> class B(A): pass
>>> class C(B): pass
>>> class D(A):
...     def test(self): print 1
>>> class E(D,C): pass

>>> get_class_that_defined_method(A().test)
<class '__main__.A'>
>>> get_class_that_defined_method(A.test)
<class '__main__.A'>
>>> get_class_that_defined_method(B.test)
<class '__main__.A'>
>>> get_class_that_defined_method(C.test)
<class '__main__.A'>
>>> get_class_that_defined_method(D.test)
<class '__main__.D'>
>>> get_class_that_defined_method(E().test)
<class '__main__.D'>
>>> get_class_that_defined_method(E.test)
<class '__main__.D'>
>>> E().test()
1

A solução de Alex retorna os mesmos resultados. Contanto que a abordagem de Alex possa ser usada, eu a usaria em vez desta.

Estani
fonte
Cls().meth.__self__apenas fornece a instância de Clsque está ligada a essa instância específica de meth. É análogo a Cls().meth.im_class. Em caso afirmativo class SCls(Cls), SCls().meth.__self__obterá uma SClsinstância, não uma Clsinstância. O que o OP quer é obter Cls, o que parece estar disponível apenas caminhando pelo MRO como @Alex Martelli faz.
Silas Ray
@ sr2222 Você está certo. Eu modifiquei a resposta porque já comecei, embora eu ache que a solução do Alex é mais compacta.
Estani
1
É uma boa solução se você precisa evitar importações, mas como você está apenas reimplementando o MRO, não há garantia de que funcionará para sempre. O MRO provavelmente permanecerá o mesmo, mas já foi alterado uma vez no passado do Python e, se for alterado novamente, esse código resultará em bugs sutis e difusos.
Silas Ray
Uma e outra vez, "cenários muito improváveis" acontecem na programação. Raramente, causando desastres. Em geral, o padrão de pensamento "XY.Z% nunca vai acontecer" é uma ferramenta de pensamento extremamente ruim ao fazer a codificação. Não use isso. Escreva código 100% correto.
ulidtko
@ulidtko Eu acho que você interpretou mal a explicação. Não se trata de correção, mas de velocidade. Não existe uma solução "perfeita" que se adapte a todos os casos, caso contrário, por exemplo, haverá apenas um algoritmo de classificação. A solução proposta aqui deve ser mais rápida no caso "raro". Uma vez que a velocidade vem em uma porcentagem de 99% de todos os casos após a legibilidade, esta solução pode ser uma solução melhor "apenas" nesse caso raro. O código, caso você não tenha lido, está 100% correto, se é isso que você temia.
estani de
6

Não sei por que ninguém mencionou isso ou por que a resposta principal tem 50 votos positivos quando é lenta como o diabo, mas você também pode fazer o seguinte:

def get_class_that_defined_method(meth):
    return meth.im_class.__name__

Para python 3, acredito que isso mudou e você precisará dar uma olhada .__qualname__.

Nick Chapman
fonte
2
Hmm. Não estou vendo a classe de definição quando faço isso no python 2.7 - Estou recebendo a classe em que o método foi chamado, não aquela em que está definido ...
F1Rumors
5

No Python 3, se você precisa do objeto de classe real, pode fazer:

import sys
f = Foo.my_function
vars(sys.modules[f.__module__])[f.__qualname__.split('.')[0]]  # Gets Foo object

Se a função pudesse pertencer a uma classe aninhada, você precisaria iterar da seguinte maneira:

f = Foo.Bar.my_function
vals = vars(sys.modules[f.__module__])
for attr in f.__qualname__.split('.')[:-1]:
    vals = vals[attr]
# vals is now the class Foo.Bar
Matthew D. Scholefield
fonte
2

Python 3

Resolvido de uma forma muito simples:

str(bar.foo_method).split(" ", 3)[-2]

Isto dá

'FooClass.foo_method'

Divida no ponto para obter a classe e o nome da função separadamente

firelynx
fonte
Uau, que defesa!
user3712978
1

Comecei fazendo algo um tanto semelhante, basicamente a ideia era verificar se um método em uma classe base havia sido implementado ou não em uma subclasse. Da maneira como fiz originalmente, não consegui detectar quando uma classe intermediária estava realmente implementando o método.

Minha solução alternativa foi bastante simples; definir um atributo de método e testar sua presença posteriormente. Aqui está uma simplificação de tudo:

class A():
    def method(self):
        pass
    method._orig = None # This attribute will be gone once the method is implemented

    def run_method(self, *args, **kwargs):
        if hasattr(self.method, '_orig'):
            raise Exception('method not implemented')
        self.method(*args, **kwargs)

class B(A):
    pass

class C(B):
    def method(self):
        pass

class D(C):
    pass

B().run_method() # ==> Raises Exception: method not implemented
C().run_method() # OK
D().run_method() # OK

ATUALIZAÇÃO: Realmente chame method()de run_method()(não é esse o espírito?) E passe todos os argumentos não modificados para o método.

PS: Esta resposta não responde diretamente à pergunta. IMHO, existem duas razões pelas quais alguém gostaria de saber qual classe definiu um método; o primeiro é apontar o dedo para uma classe no código de depuração (como no tratamento de exceções) e o segundo é determinar se o método foi reimplementado (onde método é um esboço para ser implementado pelo programador). Essa resposta resolve o segundo caso de uma maneira diferente.

Thomas Guyot-Sionnest
fonte