Как я могу получить список всех классов в текущем модуле в Python?

301

Я видел множество примеров людей, извлекающих все классы из модуля, обычно что-то вроде:

# foo.py
class Foo:
    pass

# test.py
import inspect
import foo

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

Потрясающие.

Но я не могу узнать, как получить все классы из текущего модуля.

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

Это, наверное, что-то действительно очевидное, но я не смог ничего найти. Может кто-нибудь мне помочь?

mcccclean
источник
2
Для такой функции был PEP , но он был отклонен.
Гари ван дер Мерве
Что не так с чтением источника для "class"? Почему это не сработает?
S.Lott
67
Я предполагаю, что вопрос заключается в желании автоматизировать какую-то задачу, поэтому важно, чтобы это было сделано программно. Предположительно, спрашивающий считает, что делать это вручную, читая исходный код глазами, может быть повторяющимся, подверженным ошибкам или отнимающим много времени.
Джонатан Хартли

Ответы:

386

Попробуй это:

import sys
current_module = sys.modules[__name__]

В вашем контексте:

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

И даже лучше:

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

Потому что inspect.getmembers()принимает предикат.

Надя Алрамли
источник
9
Если я импортирую классы в этом модуле на уровне модуля (то есть from optparse import OptionParser), эти модули будут включены в список печати. Как я могу избежать этого?
Крис
5
@phasetwenty вместо inspect.isclass у вас может быть что-то вроде:inspect.getmembers(sys.modules[__name__], lambda member: member.__module__ == __name__ and isnpect.isclass)
Надя Алрамли
1
но dict(inspect.getmembers(sys.modules[__name__])) == globals()всегда Trueтак зачем импорт?
Кодзиро
16
Надя ответит почти правильно. Лучше: inspect.getmembers(sys.modules[__name__], lambda member: inspect.isclass(member) and member.__module__ == __name__
Уильям Буддингтон
1
@JohnM. потому что Надя забыла позвонить isclass.
Алекс Холл
20

Что о

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

?

Краб
источник
Это то, что я обычно делаю. Другие ответы кажутся гораздо более «чистыми», хотя и не знали о них.
Мизипзор
1
Мне кажется, достаточно чисто, особенно если вы isinstance(obj, types.ClassType)
включите
4
Мне больше нравится этот ответ, потому что он будет работать, даже если текущий модуль не был помещен в sys.modules, например, из docs.python.org/2/library/functions.html#execfile
Крис Смит
@ChrisSmith В частности, сегодня я обнаружил, что некоторые отладчики, например, pudbзапускают вашу программу таким образом, что приводит к sys.modulesслучайному сбою кода при отладке. globals()кажется немного некрасивым, но это кажется намного более надежным.
Сорен Бьорнстад
15

Я не знаю, есть ли «правильный» способ сделать это, но ваш фрагмент находится на правильном пути: просто добавьте import fooв foo.py, сделайте inspect.getmembers(foo), и он должен работать нормально.

int3
источник
Я бы подумал, что это создаст круговую зависимость или что-то в этом роде, но это работает!
Mcccclean
Причина, по которой вы не получаете циклическую зависимость или цикл импорта, заключается в том, что после импорта модуля он добавляется в глобальное пространство имен. Когда импортированный модуль выполняется и получает команду «import foo», он пропускает импорт, поскольку модуль уже доступен в глобальных переменных. Если вы выполняете foo как main (как скрипт), модуль фактически запускается дважды, потому что когда вы переходите к 'import foo', main будет в глобальном пространстве имен, но не foo. После 'import foo' и ' main ', и 'foo' будут находиться в пространстве имен globals.
Galinden
11

Я смог получить все, что мне нужно от dirвстроенного плюса 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)

Тем не менее, он выглядит как шарик:

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
источник
6
import pyclbr
print(pyclbr.readmodule(__name__).keys())

Обратите внимание, что модуль браузера класса Python stdlib использует статический анализ исходного кода, поэтому он работает только для модулей, которые поддерживаются реальным .pyфайлом.

ncoghlan
источник
4

Если вы хотите иметь все классы, принадлежащие текущему модулю, вы можете использовать это:

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)

Если вы используете ответ Nadia и импортировали другие классы в свой модуль, эти классы также будут импортированы.

Вот почему member.__module__ == __name__он добавляется к предикату, используемому для is_class_member. Это утверждение проверяет, что класс действительно принадлежит модулю.

Предикат - это функция (вызываемая), которая возвращает логическое значение.

Бенджи Малка
источник
3

Другое решение, которое работает в Python 2 и 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
источник
Это не работает в 3.6.8. Я не получаю ошибку модуля.
Авирал Сривастава
3

Это строка, которую я использую, чтобы получить все классы, которые были определены в текущем модуле (т.е. не импортированы). Согласно PEP-8, он немного длинный, но вы можете изменить его по своему усмотрению.

import sys
import inspect

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

Это дает вам список имен классов. Если вы хотите, чтобы сами объекты класса просто оставляли объект obj.

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

Это было более полезным в моем опыте.

Остин Макиллоп
источник
1
import Foo 
dir(Foo)

import collections
dir(collections)
Авинаш Конеру
источник
0

Я думаю, что вы можете сделать что-то подобное.

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

если вам нужны собственные занятия

Rain0Ash
источник
0

Я часто пишу утилиты командной строки, где первый аргумент предназначен для ссылки на один из множества различных классов. Например ./something.py feature command —-arguments, где Featureкласс и commandметод этого класса. Вот базовый класс, который делает это легко.

Предполагается, что этот базовый класс находится в каталоге вместе со всеми его подклассами. Затем вы можете позвонить, ArgBaseClass(foo = bar).load_subclasses()который вернет словарь. Например, если каталог выглядит так:

  • arg_base_class.py
  • feature.py

Предполагая feature.pyреализации class Feature(ArgBaseClass), тогда приведенный выше вызов load_subclassesвернется { 'feature' : <Feature object> }. То же самое kwargs( foo = bar) будет передано в Featureкласс.

#!/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
Зейн Клас
источник