Eric McLachlan
Eric McLachlan

Reputation: 3530

In Python, how do I get the list of classes defined within a particular file?

If a file myfile.py contains:

class A(object):
  # Some implementation

class B (object):
  # Some implementation

How can I define a method so that, given myfile.py, it returns [A, B]?

Here, the returned values for A and B can be either the name of the classes or the type of the classes.

(i.e. type(A) = type(str) or type(A) = type(type))

Upvotes: 19

Views: 19446

Answers (5)

User2321
User2321

Reputation: 3062

In case importing the file throws errors or is not desirable, one could use the following:

import pyclbr
[k for k, v in pyclbr.readmodule('myfile.py').items() if isinstance(v, pyclbr.Class)]

Upvotes: 0

user4006718
user4006718

Reputation: 16

Just building on the answers above.

If you need a list of the classes defined within the module (file), i.e. not just those present in the module namespace, and you want the list within that module, i.e. using reflection, then the below will work under both __name__ == __main__ and __name__ == <module> cases.

import sys, inspect

# You can pass a lambda function as the predicate for getmembers()
[name, cls in inspect.getmembers(sys.modules[__name__], lambda x: inspect.isclass(x) and (x.__module__ == __name__))]

In my very specific use case of registering classes to a calling framework, I used as follows:

def register():    
    myLogger.info(f'Registering classes defined in module {__name__}')
    for name, cls in inspect.getmembers(sys.modules[__name__], lambda x: inspect.isclass(x) and (x.__module__ == __name__)):
        myLogger.debug(f'Registering class {cls} with name {name}')
        <framework>.register_class(cls)

Upvotes: 0

panda-34
panda-34

Reputation: 4219

You can get both:

import importlib, inspect
for name, cls in inspect.getmembers(importlib.import_module("myfile"), inspect.isclass):

you may additionally want to check:

if cls.__module__ == 'myfile'

Upvotes: 32

Eric McLachlan
Eric McLachlan

Reputation: 3530

In case it helps someone else. Here is the final solution that I used. This method returns all classes defined in a particular package.

I keep all of the subclasses of X in a particular folder (package) and then, using this method, I can load all the subclasses of X, even if they haven't been imported yet. (If they haven't been imported yet, they cannot be accessible via __all__; otherwise things would have been much easier).

import importlib, os, inspect

def get_modules_in_package(package_name: str):
    files = os.listdir(package_name)
    for file in files:
        if file not in ['__init__.py', '__pycache__']:
            if file[-3:] != '.py':
                continue

            file_name = file[:-3]
            module_name = package_name + '.' + file_name
            for name, cls in inspect.getmembers(importlib.import_module(module_name), inspect.isclass):
                if cls.__module__ == module_name:
                    yield cls

Upvotes: 9

match
match

Reputation: 11070

It's a bit long-winded, but you first need to load the file as a module, then inspect its methods to see which are classes:

import inspect
import importlib.util

# Load the module from file
spec = importlib.util.spec_from_file_location("foo", "foo.py")
foo = importlib.util.module_from_spec(spec)
spec.loader.exec_module(foo)

# Return a list of all attributes of foo which are classes
[x for x in dir(foo) if inspect.isclass(getattr(foo, x))]

Upvotes: 2

Related Questions