¿Cómo puedo obtener una lista de todas las clases dentro del módulo actual en Python?

301

He visto muchos ejemplos de personas que extraen todas las clases de un módulo, generalmente algo así como:

# foo.py
class Foo:
    pass

# test.py
import inspect
import foo

for name, obj in inspect.getmembers(foo):
    if inspect.isclass(obj):
        print obj

Increíble.

Pero no puedo descubrir cómo obtener todas las clases del módulo actual .

# foo.py
import inspect

class Foo:
    pass

def print_classes():
    for name, obj in inspect.getmembers(???): # what do I do here?
        if inspect.isclass(obj):
            print obj

# test.py
import foo

foo.print_classes()

Esto es probablemente algo realmente obvio, pero no he podido encontrar nada. ¿Puede alguien ayudarme?

mcccclean
fuente
2
Había una PEP para una característica como esta, pero fue rechazada.
Gary van der Merwe el
¿Qué tiene de malo leer la fuente "class"? ¿Por qué eso no funciona?
S.Lott
67
Supongo que la pregunta es sobre querer automatizar alguna tarea, por lo que es importante que se realice mediante programación. Presumiblemente, el interlocutor piensa que hacerlo manualmente, al leer el código fuente con los ojos, puede ser repetitivo, propenso a errores o lento.
Jonathan Hartley

Respuestas:

386

Prueba esto:

import sys
current_module = sys.modules[__name__]

En tu contexto:

import sys, inspect
def print_classes():
    for name, obj in inspect.getmembers(sys.modules[__name__]):
        if inspect.isclass(obj):
            print(obj)

Y aún mejor:

clsmembers = inspect.getmembers(sys.modules[__name__], inspect.isclass)

Porque inspect.getmembers()toma un predicado.

Nadia Alramli
fuente
99
Si importo clases en este módulo a nivel de módulo (es decir, from optparse import OptionParser) esos módulos se incluyen en la lista de impresión. ¿Cómo podría evitar eso?
Chris
55
@phasetwenty, en lugar de inspect.isclass puedes tener algo como:inspect.getmembers(sys.modules[__name__], lambda member: member.__module__ == __name__ and isnpect.isclass)
Nadia Alramli
1
pero dict(inspect.getmembers(sys.modules[__name__])) == globals()siempre es Trueasí, ¿por qué las importaciones?
kojiro
16
La respuesta de Nadia es casi correcta. Mejor: inspect.getmembers(sys.modules[__name__], lambda member: inspect.isclass(member) and member.__module__ == __name__
William Budington
1
@JohnM. porque Nadia olvidó llamar isclass.
Alex Hall
20

Qué pasa

g = globals().copy()
for name, obj in g.iteritems():

?

Cangrejo
fuente
Esto es lo que suelo hacer. Sin embargo, las otras respuestas parecen mucho más "limpias", no las conocía.
Mizipzor el
1
Me parece bastante limpio, especialmente si se filtraisinstance(obj, types.ClassType)
kojiro
44
Me gusta más esta respuesta porque funcionará incluso si el módulo actual no se ha puesto en sys.modules, por ejemplo, de docs.python.org/2/library/functions.html#execfile
Chris Smith
@ChrisSmith En particular, descubrí hoy que algunos depuradores como pudbejecutar su programa de esta manera, lo que da como resultado que el código se sys.modulesrompa al azar mientras lo depura. globals()parece un poco feo, pero parece ser mucho más confiable.
Soren Bjornstad
15

No sé si hay una forma 'adecuada' de hacerlo, pero su fragmento está en el camino correcto: solo agregue import fooa foo.py, do inspect.getmembers(foo), y debería funcionar bien.

int3
fuente
Vaya, hubiera pensado que esto crearía una dependencia circular o algo así, ¡pero funciona!
mcccclean el
La razón por la que no obtiene una dependencia circular o un ciclo de importación es que una vez que importa un módulo, se agrega al espacio de nombres global. Cuando el módulo importado se ejecuta y llega a 'import foo', omite la importación porque el módulo ya está disponible en globales. Si ejecuta foo como main (como un script), el módulo en realidad se ejecuta dos veces porque cuando llegue a 'import foo' main estará en el espacio de nombres global pero no foo. Después de 'import foo', tanto ' main ' como 'foo' estarán en el espacio de nombres global.
Galinden
11

Pude obtener todo lo que necesitaba del dirplus incorporado getattr.

# Works on pretty much everything, but be mindful that 
# you get lists of strings back

print dir(myproject)
print dir(myproject.mymodule)
print dir(myproject.mymodule.myfile)
print dir(myproject.mymodule.myfile.myclass)

# But, the string names can be resolved with getattr, (as seen below)

Sin embargo, parece una bola de pelo:

def list_supported_platforms():
    """
        List supported platforms (to match sys.platform)

        @Retirms:
            list str: platform names
    """
    return list(itertools.chain(
        *list(
            # Get the class's constant
            getattr(
                # Get the module's first class, which we wrote
                getattr(
                    # Get the module
                    getattr(platforms, item),
                    dir(
                        getattr(platforms, item)
                    )[0]
                ),
                'SYS_PLATFORMS'
            )
            # For each include in platforms/__init__.py 
            for item in dir(platforms)
            # Ignore magic, ourselves (index.py) and a base class.
            if not item.startswith('__') and item not in ['index', 'base']
        )
    ))
ThorSummoner
fuente
6
import pyclbr
print(pyclbr.readmodule(__name__).keys())

Tenga en cuenta que el módulo de navegador de clase Python de stdlib utiliza análisis de fuente estática, por lo que solo funciona para módulos respaldados por un .pyarchivo real .

ncoghlan
fuente
4

Si desea tener todas las clases, que pertenecen al módulo actual, puede usar esto:

import sys, inspect
def print_classes():
    is_class_member = lambda member: inspect.isclass(member) and member.__module__ == __name__
    clsmembers = inspect.getmembers(sys.modules[__name__], is_class_member)

Si usa la respuesta de Nadia y estaba importando otras clases en su módulo, esas clases también se importarán.

Por eso member.__module__ == __name__se agrega al predicado utilizado enis_class_member . Esta declaración comprueba que la clase realmente pertenece al módulo.

Un predicado es una función (invocable), que devuelve un valor booleano.

Benjy Malca
fuente
3

Otra solución que funciona en Python 2 y 3:

#foo.py
import sys

class Foo(object):
    pass

def print_classes():
    current_module = sys.modules[__name__]
    for key in dir(current_module):
        if isinstance( getattr(current_module, key), type ):
            print(key)

# test.py
import foo
foo.print_classes()
Florian
fuente
Esto no funciona en 3.6.8. No recibo ningún error de módulo.
Aviral Srivastava
3

Esta es la línea que uso para obtener todas las clases que se han definido en el módulo actual (es decir, no importado). Según PEP-8, es un poco largo, pero puede cambiarlo como mejor le parezca.

import sys
import inspect

classes = [name for name, obj in inspect.getmembers(sys.modules[__name__], inspect.isclass) 
          if obj.__module__ is __name__]

Esto le da una lista de los nombres de clase. Si desea que los objetos de la clase se mantengan obj en su lugar.

classes = [obj for name, obj in inspect.getmembers(sys.modules[__name__], inspect.isclass)
          if obj.__module__ is __name__]

Esto ha sido más útil en mi experiencia.

Austin Mackillop
fuente
1
import Foo 
dir(Foo)

import collections
dir(collections)
Avinash Koneru
fuente
0

Creo que puedes hacer algo como esto.

class custom(object):
    __custom__ = True
class Alpha(custom):
    something = 3
def GetClasses():
    return [x for x in globals() if hasattr(globals()[str(x)], '__custom__')]
print(GetClasses())`

si necesitas clases propias

Rain0Ash
fuente
0

Con frecuencia me encuentro escribiendo utilidades de línea de comandos en donde el primer argumento está destinado a referirse a una de muchas clases diferentes. Por ejemplo ./something.py feature command —-arguments, donde Featurees una clase y commandes un método en esa clase. Aquí hay una clase base que facilita esto.

Se supone que esta clase base reside en un directorio junto con todas sus subclases. Luego puede llamar, ArgBaseClass(foo = bar).load_subclasses()lo que devolverá un diccionario. Por ejemplo, si el directorio se ve así:

  • arg_base_class.py
  • feature.py

Suponiendo feature.pyimplementos class Feature(ArgBaseClass), entonces la invocación anterior de load_subclassesregresará { 'feature' : <Feature object> }. Lo mismo kwargs( foo = bar) se pasará a la Featureclase.

#!/usr/bin/env python3
import os, pkgutil, importlib, inspect

class ArgBaseClass():
    # Assign all keyword arguments as properties on self, and keep the kwargs for later.
    def __init__(self, **kwargs):
        self._kwargs = kwargs
        for (k, v) in kwargs.items():
            setattr(self, k, v)
        ms = inspect.getmembers(self, predicate=inspect.ismethod)
        self.methods = dict([(n, m) for (n, m) in ms if not n.startswith('_')])

    # Add the names of the methods to a parser object.
    def _parse_arguments(self, parser):
        parser.add_argument('method', choices=list(self.methods))
        return parser

    # Instantiate one of each of the subclasses of this class.
    def load_subclasses(self):
        module_dir = os.path.dirname(__file__)
        module_name = os.path.basename(os.path.normpath(module_dir))
        parent_class = self.__class__
        modules = {}
        # Load all the modules it the package:
        for (module_loader, name, ispkg) in pkgutil.iter_modules([module_dir]):
            modules[name] = importlib.import_module('.' + name, module_name)

        # Instantiate one of each class, passing the keyword arguments.
        ret = {}
        for cls in parent_class.__subclasses__():
            path = cls.__module__.split('.')
            ret[path[-1]] = cls(**self._kwargs)
        return ret
Zane Claes
fuente