Encuentra la clase en la que se define un método.

Quiero averiguar el tipo de clase en la que se define un determinado método (en esencia, el ámbito estático adjunto del método), desde dentro del propio método, y sin especificarlo explícitamente, por ejemplo

class SomeClass: def do_it(self): cls = enclosing_class() # <-- I need this. print(cls) class DerivedClass(SomeClass): pass obj = DerivedClass() # I want this to print 'SomeClass'. obj.do_it() 

es posible?

Si necesita esto en Python 3.x, vea mi otra respuesta: la celda de cierre __class__ es todo lo que necesita.


Si necesita hacer esto en CPython 2.6-2.7, la respuesta de RickyA es cercana, pero no funciona, porque se basa en el hecho de que este método no está anulando a ningún otro método del mismo nombre. Intente agregar un método Foo.do_it en su respuesta, e imprimirá Foo , no SomeClass

La forma de resolverlo es encontrar el método cuyo objeto de código es idéntico al objeto de código del marco actual:

 def do_it(self): mro = inspect.getmro(self.__class__) method_code = inspect.currentframe().f_code method_name = method_code.co_name for base in reversed(mro): try: if getattr(base, method_name).func_code is method_code: print(base.__name__) break except AttributeError: pass 

(Tenga en cuenta que AttributeError puede do_it ya sea porque la base no tiene algo llamado do_it , o si la base tiene algo que se llama do_it que no es una función y, por lo tanto, no tiene un func_code . Pero no nos importa cuál; , la base no es el partido que estamos buscando.)

Esto puede funcionar en otras implementaciones de Python 2.6+. Python no requiere que existan objetos de marco, y si no lo hacen, inspect.currentframe() devolverá None . Y estoy bastante seguro de que tampoco es necesario que existan objetos de código, lo que significa que func_code podría ser None .

Mientras tanto, si desea usar esto en 2.7+ y 3.0+, cambie ese func_code a __code__ , pero eso romperá la compatibilidad con la versión anterior 2.x.


Si necesita CPython 2.5 o una inpsect anterior, simplemente puede reemplazar las llamadas de inpsect con los atributos de CPython específicos de la implementación:

 def do_it(self): mro = self.__class__.mro() method_code = sys._getframe().f_code method_name = method_code.co_name for base in reversed(mro): try: if getattr(base, method_name).func_code is method_code: print(base.__name__) break except AttributeError: pass 

Tenga en cuenta que este uso de mro() no funcionará en clases clásicas; Si realmente quiere manejar esos (lo que realmente no debería querer …), tendrá que escribir su propia función mro que simplemente mro la jerarquía de la vieja escuela … o simplemente copiarla de la fuente de inspect 2.6.

Esto solo funcionará en implementaciones de Python 2.x que se inclinan hacia atrás para ser compatibles con CPython … pero eso incluye al menos PyPy. inspect debería ser más portátil, pero luego, si una implementación va a definir los frame y los objetos de code con los mismos atributos que CPython’s para que puedan admitir todos los inspect , no hay una buena razón para no crear atributos y proporcionar sys._getframe en el primer lugar…

Primero, esto es casi una mala idea, y no la forma en que quieres resolver lo que intentas resolver, pero te niegan a decirnos sobre …

Dicho esto, hay una manera muy fácil de hacerlo, al menos en Python 3.0+. (Si necesita 2.x, vea mi otra respuesta.)

Tenga en cuenta que el super Python 3.x tiene que ser capaz de hacer esto de alguna manera. ¿De qué otra manera podría super() significar super(THISCLASS, self) , donde esta THISCLASS es exactamente lo que estás pidiendo? *

Ahora, hay muchas maneras en que se podría implementar super … pero PEP 3135 detalla una especificación sobre cómo implementarlo:

Cada función tendrá una celda llamada __class__ que contiene el objeto de clase en el que se define la función.

Esto no es parte de los documentos de referencia de Python, por lo que alguna otra implementación de Python 3.x podría hacerlo de otra manera … pero al menos a partir de la versión 3.2+, todavía tienen que tener __class__ en las funciones, porque Crear el objeto de clase dice explícitamente :

Este objeto de clase es el que será referenciado por la forma de argumento cero de super() . __class__ es una referencia de cierre implícita creada por el comstackdor si cualquier método en el cuerpo de una clase se refiere a __class__ o super . Esto permite que la forma de argumento cero de super() identifique correctamente la clase que se define en función del scope léxico, mientras que la clase o instancia que se usó para realizar la llamada actual se identifica en función del primer argumento que se pasó al método.

(Y, no hace falta decirlo, así es como al menos CPython 3.0-3.5 y PyPy3 2.0-2.1 implementan super todos modos).

 In [1]: class C: ...: def f(self): ...: print(__class__) In [2]: class D(C): ...: pass In [3]: D().f()  

Por supuesto, esto obtiene el objeto de clase real, no el nombre de la clase, que aparentemente es lo que buscabas. Pero eso es fácil; solo tiene que decidir si quiere decir __class__.__name__ o __class__.__qualname__ (en este simple caso son idénticos) e imprimir eso.


* De hecho, este fue uno de los argumentos en su contra: que la única forma plausible de hacerlo sin cambiar la syntax del idioma era agregar una nueva celda de cierre a cada función, o requerir algunos hacks de marco horribles que tal vez ni siquiera sean factibles. En otras implementaciones de Python. No puedes simplemente usar la magia del comstackdor, porque no hay forma de que el comstackdor pueda decir que alguna expresión arbitraria evaluará la función super en tiempo de ejecución …

Si puedes usar el método de @abarnert, hazlo.

De lo contrario, puedes usar alguna introspección hardcore (para python2.7):

 import inspect from http://stackoverflow.com/a/22898743/2096752 import getMethodClass def enclosing_class(): frame = inspect.currentframe().f_back caller_self = frame.f_locals['self'] caller_method_name = frame.f_code.co_name return getMethodClass(caller_self.__class__, caller_method_name) class SomeClass: def do_it(self): print(enclosing_class()) class DerivedClass(SomeClass): pass DerivedClass().do_it() # prints 'SomeClass' 

Obviamente, es probable que esto genere un error si:

  • Llamado desde una función regular / staticmethod / classmethod
  • la función de llamada tiene un nombre diferente para self (como bien señalado por @abarnert, esto se puede resolver utilizando frame.f_code.co_varnames[0] )

Puede hacer lo que @mgilson sugirió o tomar otro enfoque.

 class SomeClass: pass class DerivedClass(SomeClass): pass 

Esto hace que SomeClass sea ​​la clase base para DerivedClass .
Cuando normalmente intenta obtener __class__.name__ , se referirá a la clase derivada en lugar de a la principal.

Cuando llamas a do_it() , es realmente pasar DerivedClass como self, razón por la cual es muy probable que DerivedClass imprimiendo DerivedClass .

En su lugar, intente esto:

 class SomeClass: pass class DerivedClass(SomeClass): def do_it(self): for base in self.__class__.__bases__: print base.__name__ obj = DerivedClass() obj.do_it() # Prints SomeClass 

Editar:
Después de leer tu pregunta un par de veces más, creo que entiendo lo que quieres.

 class SomeClass: def do_it(self): cls = self.__class__.__bases__[0].__name__ print cls class DerivedClass(SomeClass): pass obj = DerivedClass() obj.do_it() # prints SomeClass 

[Editado] Una solución algo más genérica:

 import inspect class Foo: pass class SomeClass(Foo): def do_it(self): mro = inspect.getmro(self.__class__) method_name = inspect.currentframe().f_code.co_name for base in reversed(mro): if hasattr(base, method_name): print(base.__name__) break class DerivedClass(SomeClass): pass class DerivedClass2(DerivedClass): pass DerivedClass().do_it() >> 'SomeClass' DerivedClass2().do_it() >> 'SomeClass' SomeClass().do_it() >> 'SomeClass' 

Esto falla cuando alguna otra clase en la stack tiene el atributo “do_it”, ya que este es el nombre de la señal para dejar de caminar el mro.

Lo siento por escribir una respuesta más, pero aquí está cómo hacer lo que realmente quieres hacer, en lugar de lo que pediste:

se trata de agregar instrumentación a una base de código para poder generar informes de conteos de invocación de métodos, con el fin de verificar ciertos invariantes aproximados de tiempo de ejecución (por ejemplo, “el número de veces que se ejecuta el método ClassA.x () es aproximadamente igual al el número de veces que se ejecuta el método ClassB.y () en el curso de una ejecución de un progtwig complicado).

La forma de hacerlo es hacer que su función de instrumentación inyecte la información de forma estática. Después de todo, tiene que conocer la clase y el método en el que está inyectando el código.

Tendré que instrumentar muchas clases a mano, y para evitar errores quiero evitar escribir los nombres de las clases en todas partes. En esencia, es la misma razón por la que escribir super () es preferible a escribir super (ClassX, self).

Si su función de instrumentación es “hacerlo manualmente”, lo primero que desea convertirlo en una función real en lugar de hacerlo manualmente. Como obviamente solo necesita inyección estática, el uso de un decorador, ya sea en la clase (si desea instrumentar todos los métodos) o en cada método (si no lo hace) lo haría agradable y legible. (O, si desea instrumentar todos los métodos de cada clase, puede definir una metaclase y hacer que sus clases de raíz la utilicen, en lugar de decorar cada clase).

Por ejemplo, aquí hay una manera fácil de instrumentar cada método de una clase:

 import collections import functools import inspect _calls = {} def inject(cls): cls._calls = collections.Counter() _calls[cls.__name__] = cls._calls for name, method in cls.__dict__.items(): if inspect.isfunction(method): @functools.wraps(method) def wrapper(*args, **kwargs): cls._calls[name] += 1 return method(*args, **kwargs) setattr(cls, name, wrapper) return cls @inject class A(object): def f(self): print('Af here') @inject class B(A): def f(self): print('Bf here') @inject class C(B): pass @inject class D(C): def f(self): print('Df here') d = D() df() Bf(d) print(_calls) 

La salida:

 {'A': Counter(), 'C': Counter(), 'B': Counter({'f': 1}), 'D': Counter({'f': 1})} 

Exactamente lo que querías, ¿verdad?