Як я можу отримати список усіх класів у поточному модулі в 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()

Це, мабуть, щось дійсно очевидно, але я нічого не зміг знайти. Хтось може мені допомогти?


2
Для такої функції був PEP , але він був відхилений.
Гарі ван дер Мерве

Що не в тому, щоб прочитати джерело "class"? Чому це не вийде?
S.Lott

66
Я здогадуюсь, що питання полягає у бажанні автоматизувати якесь завдання, тому важливо, щоб це було зроблено програмно. Імовірно, запитуючий вважає, що робити це вручну, читаючи вихідний код очима, може бути повторюваним, схильним до помилок або забирає багато часу.
Джонатан Хартлі

Відповіді:


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)
Nadia Alramli

1
але dict(inspect.getmembers(sys.modules[__name__])) == globals()це завжди True, так навіщо імпорт?
якийro

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)
kojiro

4
Мені подобається ця відповідь краще, тому що вона буде працювати, навіть якщо поточний модуль не розміщено в sys.modules, наприклад, з docs.python.org/2/library/functions.html#execfile
Кріс Сміт

@ChrisSmith Зокрема, сьогодні я виявив, що деякі налагоджувачі, такі як pudbзапускати вашу програму таким чином, призводять до того, що код використовує sys.modulesламання випадковим чином під час налагодження. globals()здається трохи потворним, але здається набагато надійнішим.
Сорен Бьорнстад

15

Я не знаю, чи є "правильний" спосіб зробити це, але ваш фрагмент стоїть на правильному шляху: просто додайте import fooдо foo.py, виконайте це inspect.getmembers(foo), і він повинен працювати добре.


О, я б подумав, що це створить кругову залежність чи щось таке, але це працює!
mcccclean

Причина, коли ви не отримуєте кругової залежності або циклу імпорту, полягає в тому, що після імпорту модуля він додається до глобального простору імен. Коли імпортований модуль виконується і потрапляє до 'import foo', він пропускає імпорт, оскільки модуль вже доступний у глобальних. Якщо ви виконуєте foo як основний (як скрипт), модуль насправді виконується двічі, тому що, коли ви перейдете до імпорту foo, головний буде знаходитись у глобальному просторі імен, але не foo. Після 'імпорту foo' обидва ' main ' і 'foo' будуть знаходитись у просторі імен глобальних мереж.
galinden

10

Мені вдалося отримати все необхідне з 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']
        )
    ))

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

Зауважте, що в браузерному модулі класу Python stdlib використовується статичний аналіз джерела, тому він працює лише для модулів, які підтримуються реальним .pyфайлом.


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)

Якщо ви використовуєте відповідь Надії і ви імпортували інші класи на своєму модулі, вони також будуть імпортовані.

Тож тому 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()

Це не працює в 3.6.8. Я не отримую помилки модуля.
Aviral Srivastava

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__]

Це було кориснішим у моєму досвіді.



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

якщо вам потрібні власні заняття


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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.