【问题标题】:how to preserve module path of a module executed as a script如何保留作为脚本执行的模块的模块路径
【发布时间】:2015-11-26 20:00:22
【问题描述】:

我有一个名为get_full_class_name(instance) 的函数,它返回instance 的完整模块限定类名。

示例 my_utils.py:

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

不幸的是,当给定在当前运行的脚本中定义的类时,此函数会失败。

示例 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()

当我运行上面的脚本时,不是打印my_module.MyClass,而是打印__main__.MyClass

$ ./my_module.py
__main__.MyClass

如果我从另一个脚本运行上述main(),我确实会得到所需的行为。

示例 run_my_module.py:

#! /usr/bin/env python

from my_module import main

if __name__ == '__main__':
    main()

运行上面的脚本得到:

$ ./run_my_module.py
my_module.MyClass

有没有一种方法可以编写get_full_class_name() 函数,使其始终返回my_module.MyClass,而不管my_module 是否作为脚本运行?

【问题讨论】:

    标签: python class module path introspection


    【解决方案1】:

    我建议使用Find Path to File Being Run 中讨论的技术来处理__name__ == '__main__' 案例。这导致了这个新的 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__])
    

    这不处理交互式会话和其他特殊情况(如从标准输入读取)。为此,您可能必须包含detect python running interactively 中讨论的技术。

    【讨论】:

      【解决方案2】:

      根据 mkiever 的回答,我最终将 get_full_class_name() 更改为您在下面看到的内容。

      如果instance.__class__.__module____main__,它不会使用它作为模块路径。相反,它使用从sys.argv[0]sys.path 中最近的目录的相对路径。

      一个问题是sys.path 总是包含sys.argv[0] 本身的目录,所以这个相对路径最终只是sys.argv[0] 的文件名部分。作为一个快速破解,下面的代码假定sys.argv[0] 目录始终是sys.path 的第一个元素,并忽略它。这似乎不安全,但是对于我的个人代码来说,更安全的选项现在太乏味了。

      任何更好的解决方案/建议将不胜感激。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-09-26
        • 1970-01-01
        • 1970-01-01
        • 2010-09-19
        • 1970-01-01
        相关资源
        最近更新 更多