Jak mogę uzyskać listę wszystkich klas w ramach bieżącego modułu w Pythonie?

301

Widziałem wiele przykładów ludzi wydobywających wszystkie klasy z modułu, zwykle coś takiego:

# foo.py
class Foo:
    pass

# test.py
import inspect
import foo

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

Niesamowite.

Ale nie mogę się dowiedzieć, jak uzyskać wszystkie klasy z bieżącego modułu.

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

To prawdopodobnie coś naprawdę oczywistego, ale nic nie znalazłem. Czy ktoś może mi pomóc?

Mccclean
źródło
2
Wystąpił PEP dla takiej funkcji, ale został on odrzucony.
Gary van der Merwe
Co jest złego w czytaniu źródła "class"? Dlaczego to nie zadziała?
S.Lott,
66
Domyślam się, że pytanie dotyczy automatyzacji jakiegoś zadania, dlatego ważne jest, aby było to zrobione programowo. Prawdopodobnie pytający uważa, że ​​robienie tego ręcznie, czytając kod źródłowy oczami, może być powtarzalne, podatne na błędy lub czasochłonne.
Jonathan Hartley,

Odpowiedzi:

386

Spróbuj tego:

import sys
current_module = sys.modules[__name__]

W twoim kontekście:

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

A nawet lepiej:

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

Ponieważ inspect.getmembers()wymaga orzeczenia.

Nadia Alramli
źródło
9
Jeśli zaimportuję klasy do tego modułu na poziomie modułu (tj. from optparse import OptionParser), Moduły te zostaną uwzględnione na liście drukowania. Jak mogę tego uniknąć?
Chris
5
@phasetwenty, zamiast inspect.isclass możesz mieć coś takiego:inspect.getmembers(sys.modules[__name__], lambda member: member.__module__ == __name__ and isnpect.isclass)
Nadia Alramli
1
ale dict(inspect.getmembers(sys.modules[__name__])) == globals()jest zawsze True, więc dlaczego import?
kojiro
16
Odpowiedź Nadii jest prawie poprawna. Lepiej: inspect.getmembers(sys.modules[__name__], lambda member: inspect.isclass(member) and member.__module__ == __name__
William Budington,
1
@JohnM. ponieważ Nadia zapomniała zadzwonić isclass.
Alex Hall
20

Co powiesz na

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

?

Krab
źródło
Tak zwykle robię. Inne odpowiedzi wydają się jednak o wiele bardziej „czyste”, nie wiem o nich.
Mizipzor,
1
Wydaje mi się, że wszystko jest czyste, szczególnie jeśli odfiltrujeszisinstance(obj, types.ClassType)
kojiro,
4
Bardziej
Chris Smith
@ChrisSmith W szczególności dzisiaj odkryłem, że niektóre debuggery, takie jak pudburuchamianie programu w ten sposób, powodują, że kod ulega przypadkowemu uszkodzeniu sys.modulespodczas debugowania. globals()wydaje się trochę brzydka, ale wydaje się być bardziej niezawodna.
Soren Bjornstad
15

Nie wiem, czy istnieje „właściwy” sposób, aby to zrobić, ale Twój fragment import fookodu jest na dobrej drodze: po prostu dodaj do foo.py, zrób inspect.getmembers(foo)i powinien działać dobrze.

int3
źródło
Zaraz, pomyślałbym, że to stworzy zależność cykliczną czy coś, ale to działa!
mcccclean
Powodem braku zależności cyklicznej lub pętli importu jest to, że po zaimportowaniu modułu jest on dodawany do globalnej przestrzeni nazw. Gdy importowany moduł jest wykonywany i przechodzi do „importu foo”, pomija import, ponieważ moduł jest już dostępny w globals. Jeśli wykonasz foo jako main (jako skrypt), moduł jest faktycznie uruchamiany dwukrotnie, ponieważ kiedy przejdziesz do 'import foo' main będzie w globalnej przestrzeni nazw, ale nie foo. Po „import foo” zarówno „ main ”, jak i „foo” znajdą się w globalnej przestrzeni nazw.
galinden
10

Byłem w stanie uzyskać wszystko, czego potrzebowałem z dirwbudowanego plus 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)

Chociaż wygląda jak sierść:

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
źródło
6
import pyclbr
print(pyclbr.readmodule(__name__).keys())

Zauważ, że moduł przeglądarki klasy Python w stdlib wykorzystuje statyczną analizę źródła, więc działa tylko dla modułów, które są wspierane przez prawdziwy .pyplik.

ncoghlan
źródło
4

Jeśli chcesz mieć wszystkie klasy należące do bieżącego modułu, możesz użyć tego:

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)

Jeśli użyjesz odpowiedzi Nadii i importujesz inne klasy do swojego modułu, klasy te również zostaną zaimportowane.

Właśnie dlatego member.__module__ == __name__jest dodawany do predykatu używanego na is_class_member. Ta instrukcja sprawdza, czy klasa naprawdę należy do modułu.

Predykat to funkcja (wywoływalna), która zwraca wartość logiczną.

Benjy Malca
źródło
3

Kolejne rozwiązanie, które działa w Pythonie 2 i 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
źródło
To nie działa w 3.6.8. Nie dostaję błędu modułu.
Aviral Srivastava
3

To jest linia, której używam, aby uzyskać wszystkie klasy, które zostały zdefiniowane w bieżącym module (tj. Nie są importowane). Według PEP-8 jest trochę długi, ale możesz go zmienić według własnego uznania.

import sys
import inspect

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

To daje listę nazw klas. Jeśli chcesz same obiekty klasy, po prostu zachowaj obj zamiast tego.

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

To było bardziej przydatne z mojego doświadczenia.

Austin Mackillop
źródło
1
import Foo 
dir(Foo)

import collections
dir(collections)
Avinash Koneru
źródło
0

Myślę, że możesz zrobić coś takiego.

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

jeśli potrzebujesz własnych zajęć

Rain0Ash
źródło
0

Często piszę narzędzia wiersza poleceń, w których pierwszy argument ma odnosić się do jednej z wielu różnych klas. Na przykład ./something.py feature command —-arguments, gdzie Featurejest klasa i commandmetoda dla tej klasy. Oto podstawowa klasa, która ułatwia to.

Zakłada się, że ta klasa bazowa znajduje się w katalogu obok wszystkich jego podklas. Możesz wtedy zadzwonić, ArgBaseClass(foo = bar).load_subclasses()co zwróci słownik. Na przykład, jeśli katalog wygląda następująco:

  • arg_base_class.py
  • feature.py

Zakładając , że feature.pyimplementuje class Feature(ArgBaseClass), wówczas powyższe wywołanie load_subclasseszwróci { 'feature' : <Feature object> }. To samo kwargs( foo = bar) zostanie przekazane do Featureklasy.

#!/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
źródło