Obtener la clase que definió el método


89

¿Cómo puedo obtener la clase que definió un método en Python?

Me gustaría que el siguiente ejemplo imprima " __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)

¿Qué versión de Python estás usando? Antes de 2.2, podía usar im_class, pero eso se cambió para mostrar el tipo de objeto self vinculado.
Kathy Van Stone

1
Bueno saber. Pero estoy usando 2.6.
Jesse Aldridge

Respuestas:


71
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

1
Gracias, me hubiera tomado un tiempo resolver esto por mi cuenta
David

¡Cuidado, no todas las clases implementan __dict__! A veces __slots__se usa. Probablemente sea mejor usarlo getattrpara probar si el método está en la clase.
Codie CodeMonkey

16
Para Python 3, consulte esta respuesta .
Yoel

27
Estoy recibiendo:'function' object has no attribute 'im_class'
Zitrax

5
En Python 2.7 no funciona. El mismo error sobre la falta de "im_class".
RedX

8

Gracias Sr2222 por señalar que me estaba perdiendo el punto ...

Aquí está el enfoque corregido que es como el de Alex pero no requiere importar nada. Sin embargo, no creo que sea una mejora, a menos que haya una gran jerarquía de clases heredadas, ya que este enfoque se detiene tan pronto como se encuentra la clase definitoria, en lugar de devolver toda la herencia como lo getmrohace. Como se dijo, este es un escenario muy poco probable.

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

Y el ejemplo:

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

La solución de Alex devuelve los mismos resultados. Siempre que se pueda usar el enfoque de Alex, lo usaría en lugar de este.


Cls().meth.__self__solo le da la instancia de Clsque está vinculada a esa instancia específica de meth. Es análogo a Cls().meth.im_class. Si es así class SCls(Cls), SCls().meth.__self__obtendrá una SClsinstancia, no una Clsinstancia. Lo que quiere el OP es obtener Cls, que parece que solo está disponible caminando por el MRO como lo hace @Alex Martelli.
Silas Ray

@ sr2222 Tienes razón. Modifiqué la respuesta como ya comencé, aunque creo que la solución de Alex es más compacta.
Estani

1
Es una buena solución si necesita evitar las importaciones, pero dado que básicamente está volviendo a implementar el MRO, no está garantizado que funcione para siempre. El MRO probablemente permanecerá igual, pero ya se cambió una vez en el pasado de Python, y si se cambia nuevamente, este código resultará en errores sutiles y omnipresentes.
Silas Ray

Una y otra vez, ocurren "escenarios muy improbables" en la programación. Rara vez, provocando desastres. En general, el patrón de pensamiento "XY.Z% nunca sucederá" es una herramienta de pensamiento extremadamente pésima cuando se hace la codificación. No lo use. Escribe un código 100% correcto.
ulidtko

@ulidtko Creo que malinterpretaste la explicación. No se trata de corrección, sino de velocidad. No existe una solución "perfecta" que se ajuste a todos los casos, de lo contrario, por ejemplo, solo habrá un algoritmo de clasificación. La solución propuesta aquí debería ser más rápida en el caso "raro". Dado que la velocidad viene en el 99% de todos los casos después de la legibilidad, esta solución podría ser una mejor solución "solo" en ese caso raro. El código, en caso de que no lo haya leído, es 100% correcto, si eso es lo que temía.
estani

6

No sé por qué nadie ha mencionado esto o por qué la respuesta principal tiene 50 votos a favor cuando es lento como el infierno, pero también puede hacer lo siguiente:

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

Para Python 3, creo que esto cambió y tendrás que investigar .__qualname__.


2
Hmm. No veo la clase definitoria cuando hago eso en Python 2.7
Obtengo

5

En Python 3, si necesita el objeto de clase real, puede hacer:

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

Si la función pudiera pertenecer a una clase anidada, necesitaría iterar de la siguiente manera:

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

1

Empecé a hacer algo similar, básicamente la idea era comprobar cada vez que un método en una clase base se había implementado o no en una subclase. Resultó que, de la forma en que lo hice originalmente, no pude detectar cuándo una clase intermedia estaba implementando el método.

En realidad, mi solución fue bastante simple; establecer un atributo de método y probar su presencia más tarde. Aquí hay una simplificación de todo:

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

ACTUALIZACIÓN: Realmente llame method()desde run_method()(¿no es ese el espíritu?) Y haga que pase todos los argumentos sin modificar al método.

PD: Esta respuesta no responde directamente a la pregunta. En mi humilde opinión, hay dos razones por las que uno querría saber qué clase define un método; el primero es señalar con el dedo a una clase en el código de depuración (como en el manejo de excepciones), y el segundo es determinar si el método se ha vuelto a implementar (donde el método es un código auxiliar destinado a ser implementado por el programador). Esta respuesta resuelve ese segundo caso de una manera diferente.


1

Python 3

Lo solucionó de una manera muy sencilla:

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

Esto da

'FooClass.foo_method'

Dividir en el punto para obtener la clase y el nombre de la función por separado


¡Vaya, qué ahorro!
user3712978
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.