Wie kann ich eine Liste aller Klassen innerhalb des aktuellen Moduls in Python erhalten?

301

Ich habe viele Beispiele von Leuten gesehen, die alle Klassen aus einem Modul extrahiert haben, normalerweise so etwas wie:

# foo.py
class Foo:
    pass

# test.py
import inspect
import foo

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

Genial.

Ich kann jedoch nicht herausfinden, wie alle Klassen aus dem aktuellen Modul abgerufen werden können.

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

Dies ist wahrscheinlich etwas wirklich Offensichtliches, aber ich konnte nichts finden. Kann mir jemand helfen?

mcccclean
quelle
2
Es gab einen PEP für eine solche Funktion, der jedoch abgelehnt wurde.
Gary van der Merwe
Was ist falsch daran, die Quelle zu lesen "class"? Warum funktioniert das nicht?
S.Lott
66
Ich vermute, bei der Frage geht es darum, eine Aufgabe automatisieren zu wollen. Daher ist es wichtig, dass sie programmgesteuert ausgeführt wird. Vermutlich ist der Fragesteller der Ansicht, dass das manuelle Ausführen des Quellcodes mit den Augen sich wiederholen, fehleranfällig oder zeitaufwändig sein kann.
Jonathan Hartley

Antworten:

386

Versuche dies:

import sys
current_module = sys.modules[__name__]

In Ihrem Kontext:

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

Und noch besser:

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

Weil inspect.getmembers()ein Prädikat braucht.

Nadia Alramli
quelle
9
Wenn ich Klassen in diesem Modul auf Modulebene (dh from optparse import OptionParser) importiere, werden diese Module in die Druckliste aufgenommen. Wie könnte ich das vermeiden?
Chris
5
@phasetwenty, anstelle von inspect.isclass können Sie etwas haben wie:inspect.getmembers(sys.modules[__name__], lambda member: member.__module__ == __name__ and isnpect.isclass)
Nadia Alramli
1
aber dict(inspect.getmembers(sys.modules[__name__])) == globals()ist immer True, warum also die Importe?
Kojiro
16
Nadias Antwort ist fast richtig. Besser: inspect.getmembers(sys.modules[__name__], lambda member: inspect.isclass(member) and member.__module__ == __name__
William Budington
1
@ JohnM. weil Nadia vergessen hat anzurufen isclass.
Alex Hall
20

Wie wäre es mit

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

?

Krabben
quelle
Das mache ich normalerweise. Die anderen Antworten scheinen jedoch viel "sauberer" zu sein, wussten nichts über sie.
Mizipzor
1
Scheint mir viel sauber zu sein, besonders wenn Sie aufisinstance(obj, types.ClassType)
Kojiro
4
Ich mag diese Antwort besser, weil sie auch dann funktioniert, wenn das aktuelle Modul nicht in sys.modules eingefügt wurde, z. B. von docs.python.org/2/library/functions.html#execfile
Chris Smith
@ChrisSmith Insbesondere habe ich heute festgestellt, dass einige Debugger pudbIhr Programm auf diese Weise ausführen, was dazu führt, dass Code sys.modulesbeim Debuggen zufällig unterbrochen wird. globals()scheint ein bisschen hässlich, aber es scheint viel zuverlässiger zu sein.
Soren Bjornstad
15

Ich weiß nicht, ob es einen "richtigen" Weg gibt, aber Ihr Snippet ist auf dem richtigen Weg: import fooFügen Sie einfach foo.py hinzu, tun Sie inspect.getmembers(foo), und es sollte gut funktionieren.

int3
quelle
Whoa, ich hätte gedacht, dass dies eine zirkuläre Abhängigkeit oder so etwas schaffen würde, aber es funktioniert!
McCcclean
Der Grund, warum Sie keine zirkuläre Abhängigkeit oder Importschleife erhalten, ist, dass ein Modul nach dem Import dem globalen Namespace hinzugefügt wird. Wenn das importierte Modul ausgeführt wird und "import foo" erhält, wird der Import übersprungen, da das Modul bereits global verfügbar ist. Wenn Sie foo als main (als Skript) ausführen, wird das Modul tatsächlich zweimal ausgeführt, da sich main beim globalen Import von foo im globalen Namespace befindet, jedoch nicht foo. Nach 'import foo' befinden sich sowohl ' main ' als auch 'foo' im globalen Namespace.
Galinden
10

Mit dem direingebauten Plus konnte ich alles bekommen, was ich brauchte 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)

Es sieht jedoch aus wie ein Haarball:

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

Beachten Sie, dass das Python-Klassenbrowsermodul der stdlib eine statische Quellenanalyse verwendet, sodass es nur für Module funktioniert, die von einer realen .pyDatei unterstützt werden.

ncoghlan
quelle
4

Wenn Sie alle Klassen haben möchten, die zum aktuellen Modul gehören, können Sie Folgendes verwenden:

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)

Wenn Sie Nadias Antwort verwenden und andere Klassen in Ihr Modul importiert haben, werden diese Klassen ebenfalls importiert.

Deshalb member.__module__ == __name__wird dem verwendeten Prädikat hinzugefügt is_class_member. Diese Anweisung überprüft, ob die Klasse wirklich zum Modul gehört.

Ein Prädikat ist eine Funktion (aufrufbar), die einen booleschen Wert zurückgibt.

Benjy Malca
quelle
3

Eine andere Lösung, die in Python 2 und 3 funktioniert:

#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
quelle
Dies funktioniert in 3.6.8 nicht. Ich erhalte keinen Modulfehler.
Aviral Srivastava
3

Dies ist die Zeile, mit der ich alle Klassen abrufe, die im aktuellen Modul definiert wurden (dh nicht importiert wurden). Laut PEP-8 ist es etwas lang, aber Sie können es nach Belieben ändern.

import sys
import inspect

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

Dies gibt Ihnen eine Liste der Klassennamen. Wenn Sie die Klassenobjekte selbst möchten, behalten Sie stattdessen obj bei.

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

Dies war meiner Erfahrung nach nützlicher.

Austin Mackillop
quelle
1
import Foo 
dir(Foo)

import collections
dir(collections)
Avinash Koneru
quelle
0

Ich denke, dass Sie so etwas tun können.

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

wenn Sie eigene Klassen benötigen

Rain0Ash
quelle
0

Ich schreibe häufig Befehlszeilenprogramme, bei denen sich das erste Argument auf eine von vielen verschiedenen Klassen beziehen soll. Zum Beispiel ./something.py feature command —-arguments, wo Featureist eine Klasse und commandist eine Methode für diese Klasse. Hier ist eine Basisklasse, die dies einfach macht.

Es wird davon ausgegangen, dass sich diese Basisklasse neben allen Unterklassen in einem Verzeichnis befindet. Sie können dann anrufen, ArgBaseClass(foo = bar).load_subclasses()wodurch ein Wörterbuch zurückgegeben wird. Wenn das Verzeichnis beispielsweise so aussieht:

  • arg_base_class.py
  • feature.py

Unter der Annahme von feature.pyImplementierungen class Feature(ArgBaseClass)wird der obige Aufruf von load_subclasseszurückgegeben { 'feature' : <Feature object> }. Das gleiche kwargs( foo = bar) wird an die FeatureKlasse übergeben.

#!/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
quelle