Comment puis-je obtenir une liste de toutes les classes du module actuel en Python?

301

J'ai vu de nombreux exemples de personnes extrayant toutes les classes d'un module, généralement quelque chose comme:

# foo.py
class Foo:
    pass

# test.py
import inspect
import foo

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

Impressionnant.

Mais je ne peux pas savoir comment obtenir toutes les classes du module actuel .

# 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()

C'est probablement quelque chose de vraiment évident, mais je n'ai rien trouvé. Quelqu'un peut m'aider?

mcccclean
la source
2
Il y avait un PEP pour une fonctionnalité comme celle-ci, mais il a été rejeté.
Gary van der Merwe
Quel est le problème avec la lecture de la source "class"? Pourquoi ça ne marche pas?
S.Lott
67
Je suppose que la question est de vouloir automatiser une tâche, il est donc important que cela se fasse par programme. On peut supposer que le questionneur pense que le faire manuellement, en lisant le code source avec les yeux, peut être répétitif, sujet à erreur ou prendre du temps.
Jonathan Hartley

Réponses:

386

Essaye ça:

import sys
current_module = sys.modules[__name__]

Dans votre contexte:

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

Et encore mieux:

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

Parce que inspect.getmembers()prend un prédicat.

Nadia Alramli
la source
9
Si j'importe des classes dans ce module au niveau du module (c'est-à-dire from optparse import OptionParser), ces modules sont inclus dans la liste d'impression. Comment pourrais-je éviter cela?
Chris
5
@phasetwenty, au lieu de inspect.isclass, vous pouvez avoir quelque chose comme:inspect.getmembers(sys.modules[__name__], lambda member: member.__module__ == __name__ and isnpect.isclass)
Nadia Alramli
1
mais dict(inspect.getmembers(sys.modules[__name__])) == globals()est toujours True, alors pourquoi les importations?
kojiro
16
La réponse de Nadia est presque correcte. Mieux: inspect.getmembers(sys.modules[__name__], lambda member: inspect.isclass(member) and member.__module__ == __name__
William Budington
1
@JohnM. parce que Nadia a oublié d'appeler isclass.
Alex Hall
20

Qu'en est-il de

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

?

Krab
la source
C'est ce que je fais d'habitude. Les autres réponses semblent cependant beaucoup plus "propres", je ne les connaissais pas.
Mizipzor
1
Cela me semble très propre, surtout si vous filtrezisinstance(obj, types.ClassType)
kojiro
4
J'aime mieux cette réponse car elle fonctionnera même si le module actuel n'a pas été placé dans sys.modules, par exemple à partir de docs.python.org/2/library/functions.html#execfile
Chris Smith
@ChrisSmith En particulier, j'ai découvert aujourd'hui que certains débogueurs, tels que l' pudbexécution de votre programme de cette façon, entraînent une interruption sys.modulesaléatoire du code lors du débogage. globals()semble un peu moche, mais il semble être beaucoup plus fiable.
Soren Bjornstad
15

Je ne sais pas s'il y a une façon «correcte» de le faire, mais votre extrait est sur la bonne voie: ajoutez simplement import fooà foo.py, do inspect.getmembers(foo), et cela devrait fonctionner correctement .

int3
la source
Whoa, j'aurais pensé que cela créerait une dépendance circulaire ou quelque chose, mais ça marche!
mcccclean
La raison pour laquelle vous n'obtenez pas de dépendance circulaire ou de boucle d'importation est qu'une fois que vous importez un module, il est ajouté à l'espace de noms global. Lorsque le module importé est exécuté et arrive à «importer foo», il saute l'importation car le module est déjà disponible dans les globaux. Si vous exécutez foo en tant que main (en tant que script), le module est en fait exécuté deux fois parce que lorsque vous arriverez à «importer foo», main sera dans l'espace de noms global mais pas foo. Après 'import foo', ' main ' et 'foo' seront dans l'espace de noms global.
galinden
11

J'ai pu obtenir tout ce dont j'avais besoin grâce au plus dirintégré 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)

Cependant, cela ressemble à une boule de poils:

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
la source
6
import pyclbr
print(pyclbr.readmodule(__name__).keys())

Notez que le module de navigateur de classe Python de stdlib utilise l'analyse de source statique, donc il ne fonctionne que pour les modules qui sont sauvegardés par un vrai .pyfichier.

ncoghlan
la source
4

Si vous voulez avoir toutes les classes qui appartiennent au module courant, vous pouvez utiliser ceci:

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 vous utilisez la réponse de Nadia et que vous importiez d'autres classes sur votre module, ces classes seront également importées.

C'est pourquoi member.__module__ == __name__est ajouté au prédicat utilisé sur is_class_member. Cette instruction vérifie que la classe appartient vraiment au module.

Un prédicat est une fonction (appelable), qui renvoie une valeur booléenne.

Benjy Malca
la source
3

Une autre solution qui fonctionne en Python 2 et 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
la source
Cela ne fonctionne pas en 3.6.8. Je n'ai aucune erreur de module.
Aviral Srivastava
3

C'est la ligne que j'utilise pour obtenir toutes les classes qui ont été définies dans le module actuel (c'est-à-dire non importées). C'est un peu long selon PEP-8 mais vous pouvez le changer comme bon vous semble.

import sys
import inspect

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

Cela vous donne une liste des noms de classe. Si vous voulez que les objets de classe eux-mêmes conservent simplement obj à la place.

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

Cela a été plus utile dans mon expérience.

Austin Mackillop
la source
1
import Foo 
dir(Foo)

import collections
dir(collections)
Avinash Koneru
la source
0

Je pense que vous pouvez faire quelque chose comme ça.

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 vous avez besoin de vos propres cours

Rain0Ash
la source
0

Je me retrouve fréquemment à écrire des utilitaires de ligne de commande dans lesquels le premier argument est destiné à faire référence à l'une des nombreuses classes différentes. Par exemple ./something.py feature command —-arguments, où Featureest une classe et commandest une méthode sur cette classe. Voici une classe de base qui rend cela facile.

L'hypothèse est que cette classe de base réside dans un répertoire à côté de toutes ses sous-classes. Vous pouvez ensuite appeler ArgBaseClass(foo = bar).load_subclasses()qui renverra un dictionnaire. Par exemple, si le répertoire ressemble à ceci:

  • arg_base_class.py
  • feature.py

En supposant des feature.pyimplémentations class Feature(ArgBaseClass), l'invocation ci-dessus de load_subclassesretourne { 'feature' : <Feature object> }. Le même kwargs( foo = bar) sera passé dans la Featureclasse.

#!/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
la source