Como posso obter uma lista de todas as classes no módulo atual no Python?


301

Eu já vi muitos exemplos de pessoas extraindo todas as classes de um módulo, geralmente algo 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

Impressionante.

Mas não consigo descobrir como obter todas as classes do módulo atual .

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

Provavelmente isso é algo realmente óbvio, mas não consegui encontrar nada. Alguém pode me ajudar?


2
Havia um PEP para um recurso como esse, mas foi rejeitado.
Gary van der Merwe

O que há de errado em ler a fonte "class"? Por que isso não funciona?
245/09 S.Lott

66
Suponho que a pergunta é sobre como automatizar alguma tarefa, por isso é importante que seja feita de maneira programática. Presumivelmente, o interlocutor pensa que fazê-lo manualmente, lendo o código-fonte com seus olhos, pode ser repetitivo, propenso a erros ou demorado.
Jonathan Hartley

Respostas:


386

Tente o seguinte:

import sys
current_module = sys.modules[__name__]

No seu contexto:

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

E ainda melhor:

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

Porque inspect.getmembers()leva um predicado.


9
Se eu importar classes neste módulo no nível do módulo (ou seja from optparse import OptionParser) , esses módulos serão incluídos na lista de impressão. Como posso evitar isso?
Chris

5
@phasetwenty, em vez de inspect.isclass você pode ter algo como:inspect.getmembers(sys.modules[__name__], lambda member: member.__module__ == __name__ and isnpect.isclass)
Nadia Alramli

1
mas dict(inspect.getmembers(sys.modules[__name__])) == globals()é sempre True, então por que as importações?
kojiro

16
A resposta de Nadia está quase correta. Melhor: inspect.getmembers(sys.modules[__name__], lambda member: inspect.isclass(member) and member.__module__ == __name__
William Budington

1
@JohnM. porque Nadia esqueceu de ligar isclass.
Alex Hall

20

A respeito

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

?


Isto é o que eu costumo fazer. As outras respostas parecem muito mais "limpas", porém, não as conheciam.
Mizipzor

1
Parece muito limpo para mim, especialmente se você filtrarisinstance(obj, types.ClassType)
kojiro

4
Eu gosto desta resposta melhor porque ele vai funcionar mesmo se o módulo atual não tem sido posto em sys.modules, por exemplo, de docs.python.org/2/library/functions.html#execfile
Chris Smith

@ ChrisSmith Em particular, descobri hoje que alguns depuradores, como o pudbseu programa , são executados dessa maneira, o que resulta em código que sys.modulesquebra aleatoriamente durante a depuração. globals()parece um pouco feio, mas parece ser muito mais confiável.
Soren Bjornstad

15

Não sei se existe uma maneira 'adequada' de fazê-lo, mas o seu trecho está no caminho certo: basta adicionar import fooao foo.py, do inspect.getmembers(foo)e ele deve funcionar bem.


Uau, eu teria pensado que isso criaria uma dependência circular ou algo assim, mas funciona!
Mcccclean

O motivo de você não obter uma dependência circular ou um loop de importação é que, depois de importar um módulo, ele é adicionado ao espaço de nomes global. Quando o módulo importado é executado e chega ao 'import foo', ele ignora a importação porque o módulo já está disponível em globais. Se você executar foo como main (como um script), o módulo será realmente executado duas vezes, porque quando você conseguir 'import foo' main estará no espaço de nomes global, mas não foo. Após 'import foo', ' main ' e 'foo' estarão no espaço de nomes global.
galinden

11

Consegui obter tudo o que precisava do 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)

No entanto, ele sai parecendo uma bola de pêlo:

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']
        )
    ))

6
import pyclbr
print(pyclbr.readmodule(__name__).keys())

Observe que o módulo do navegador de classe Python do stdlib usa análise de fonte estática, portanto, ele funciona apenas para módulos suportados por um .pyarquivo real .


4

Se você deseja ter todas as classes pertencentes ao módulo atual, você pode usar o seguinte:

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)

Se você usar a resposta da Nadia e estiver importando outras classes no seu módulo, essas classes também serão importadas.

É por isso que member.__module__ == __name__está sendo adicionado ao predicado usado is_class_member. Esta declaração verifica se a classe realmente pertence ao módulo.

Um predicado é uma função (solicitável), que retorna um valor booleano.


3

Outra solução que funciona em Python 2 e 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()

Isso não funciona no 3.6.8. Não recebo nenhum erro de módulo.
Aviral Srivastava

3

Esta é a linha que eu uso para obter todas as classes que foram definidas no módulo atual (ou seja, não importadas). É um pouco longo, de acordo com o PEP-8, mas você pode alterá-lo como achar melhor.

import sys
import inspect

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

Isso fornece uma lista dos nomes das classes. Se você quiser os próprios objetos de classe, apenas mantenha obj.

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

Isso tem sido mais útil na minha experiência.



0

Eu acho que você pode fazer algo assim.

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

se você precisar de aulas próprias


0

Costumo encontrar-me escrevendo utilitários de linha de comando em que o primeiro argumento se refere a uma de muitas classes diferentes. Por exemplo ./something.py feature command —-arguments, onde Featureé uma classe e commandé um método nessa classe. Aqui está uma classe base que facilita isso.

A suposição é que essa classe base reside em um diretório ao lado de todas as suas subclasses. Você pode ligar ArgBaseClass(foo = bar).load_subclasses()para retornar um dicionário. Por exemplo, se o diretório estiver assim:

  • arg_base_class.py
  • feature.py

Assumindo feature.pyimplementos class Feature(ArgBaseClass), a invocação acima load_subclassesretornará { 'feature' : <Feature object> }. O mesmo kwargs( foo = bar) será passado para a 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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.