2

I have a function called get_full_class_name(instance), which returns the full module-qualified class name of instance.

Example my_utils.py:

def get_full_class_name(instance):
    return '.'.join([instance.__class__.__module__,
                     instance.__class__.__name__])

Unfortunately, this function fails when given a class that's defined in a currently running script.

Example my_module.py:

#! /usr/bin/env python

from my_utils import get_full_class_name

class MyClass(object):
    pass

def main():
    print get_full_class_name(MyClass())

if __name__ == '__main__':
    main()

When I run the above script, instead of printing my_module.MyClass, it prints __main__.MyClass:

$ ./my_module.py
__main__.MyClass

I do get the desired behavior if I run the above main() from another script.

Example run_my_module.py:

#! /usr/bin/env python

from my_module import main

if __name__ == '__main__':
    main()

Running the above script gets:

$ ./run_my_module.py
my_module.MyClass

Is there a way I could write the get_full_class_name() function such that it always returns my_module.MyClass regardless of whether my_module is being run as a script?

0

2 Answers 2

0

I propose handling the case __name__ == '__main__' using the techniques discussed in Find Path to File Being Run. This results in this new my_utils:

import sys
import os.path

def get_full_class_name(instance):
    if instance.__class__.__module__ == '__main__':
        return '.'.join([os.path.basename(sys.argv[0]),
            instance.__class__.__name__])
    else:
        return '.'.join([instance.__class__.__module__,
            instance.__class__.__name__])

This does not handle interactive sessions and other special cases (like reading from stdin). For this you may have to include techniques like discussed in detect python running interactively.

Sign up to request clarification or add additional context in comments.

Comments

0

Following mkiever's answer, I ended up changing get_full_class_name() to what you see below.

If instance.__class__.__module__ is __main__, it doesn't use that as the module path. Instead, it uses the relative path from sys.argv[0] to the closest directory in sys.path.

One problem is that sys.path always includes the directory of sys.argv[0] itself, so this relative path ends up being just the filename part of sys.argv[0]. As a quick hack-around, the code below assumes that the sys.argv[0] directory is always the first element of sys.path, and disregards it. This seems unsafe, but safer options are too tedious for my personal code for now.

Any better solutions/suggestions would be greatly appreciated.

import os
import sys
from nose.tools import assert_equal, assert_not_equal

def get_full_class_name(instance):
    '''
    Returns the fully-qualified class name.

    Handles the case where a class is declared in the currently-running script
    (where instance.__class__.__module__ would be set to '__main__').
    '''

    def get_module_name(instance):

        def get_path_relative_to_python_path(path):
            path = os.path.abspath(path)
            python_paths = [os.path.abspath(p) for p in sys.path]
            assert_equal(python_paths[0],
                         os.path.split(os.path.abspath(sys.argv[0]))[0])
            python_paths = python_paths[1:]

            min_relpath_length = len(path)
            result = None
            for python_path in python_paths:
                relpath = os.path.relpath(path, python_path)
                if len(relpath) < min_relpath_length:
                    min_relpath_length = len(relpath)
                    result = os.path.join(os.path.split(python_path)[-1],
                                          relpath)

            if result is None:
                raise ValueError("Path {} doesn't seem to be in the "
                                 "PYTHONPATH.".format(path))
            else:
                return result

        if instance.__class__.__module__ == '__main__':
            script_path = os.path.abspath(sys.argv[0])
            relative_path = get_path_relative_to_python_path(script_path)
            relative_path = relative_path.split(os.sep)

            assert_not_equal(relative_path[0], '')
            assert_equal(os.path.splitext(relative_path[-1])[1], '.py')
            return '.'.join(relative_path[1:-1])
        else:
            return instance.__class__.__module__

    module_name = get_module_name(instance)
    return '.'.join([module_name, instance.__class__.__name__])

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.