【问题标题】:Make a dynamic import which is still explicit in Python进行动态导入,这在 Python 中仍然是显式的
【发布时间】:2014-02-01 02:54:53
【问题描述】:

好的,这有点复杂。

假设我的包中有一个模块:

a_package
 |-- __init__.py
 |-- a_module.py

a_module.py里面我声明A_Class

# file location: a_package/a_module.py
class A_Class():
    def say(self):
        print ("cheese")

我可以创建一个A_Class 的实例并通过这样做调用say 方法:

from a_package.a_module import A_Class
my_object = A_Class()
my_object.say() # this will display 'cheese' as expected

但是,我想做一个更动态的方法(我计划有很多包和类,并且想让代码更容易编写)。所以,我做了一个函数叫load_class

def load_class(package_name, module_name, class_name)
    result = None
    try:
        exec('from ' + package_name + '.' + module_name + ' import ' + class_name)
        exec('result = '+class_name)
    except:
        raise ImportError('Unable to load the class')
    return result

# Now, I can conveniently do this:
A_Class = load_class('a_package', 'a_module', 'A_Class')
my_object = A_Class()
my_object.say()

# or even shorter:
load_class('a_package', 'a_module', 'A_Class')().say()

程序按预期运行,但 IDE(我使用 pydev)不理解我的代码,无法进行智能感知(自动补全代码)。

如果我使用第一种方法,智能感知显然是有效的:

from a_package.a_module import A_Class
my_object = A_Class()
my_object. # when I press ".", there will be a popup to let me choose "say" method

但如果我使用第二种方法,智能感知无法为我完成:

load_class('a_package', 'a_module', 'A_Class')(). # when I press ".", nothing happened

我知道,这是在 Python 中动态导入的权衡。但是,我想知道是否有一些替代方法可以让我做第二种方法(可能不使用exec)仍然可以让通用IDE(如Pydev)的智能感知猜测类内的方法?

编辑:为什么我需要这样做? 假设我有这样一个目录结构

fruit
 |-- strawberry.py
 |-- orange.py

chocolate
 |-- cadbury.py
 |-- kitkat.py

need_dynamic.py

need_dynamic.py,我有这个脚本:

food_list = ['fruit', 'chocolate']
subfood_list = [['strawberry', 'orange'],['cadbury', 'kitkat']] 
# show food list and ask user to choose food
for i in xrange(len(food_list)):
    print i + " : " + food_list[i]
food_index = int(raw_input('chose one'))
# show subfood list and ask user to choose subfood 
for i in xrange(len(subfood_list[food_index])):
    print i + " : " + subfood_list[food_index][i]
subfood_index = int(raw_input('chose one'))
# init the class
my_class = load_class(food_list[food_index], subfood_list[food_index, subfood_index])
# the rest of the code

这只是为了简化,实际上,我打算通过获取目录来自动填充food_listsubfood_list

假设您有一个数据分类框架,并希望让用户选择他们想要使用的方法。用户还应该能够通过简单地添加 python 包一个模块来扩展框架。

我希望这个例子是合理的。

再次编辑接受的答案不能解决智能感知问题。但它展示了如何更好地编码。我认为这是IDE问题而不是python问题。我会发布另一个问题。

【问题讨论】:

  • 你的方法比from package.module import klass更好吗?后者更具语义性,不会让其他查看您的代码的程序员感到困惑。
  • 正如我所说,我的方法更容易编写。这也让我可以使用配置变量调用不同的方法和类。
  • 不要自作聪明。

标签: python intellisense dynamic-import


【解决方案1】:

你会想要使用 __import__ 内置函数:

def load_class(package, mod_name, cls_name):
    mod = __import__('.'.join((package, mod_name)))
    return getattr(mod, cls_name)

当然,如果您愿意,您可以将错误处理放回原处,老实说,我不太确定为什么您首先要这样做。对于大多数的事情,动态导入对我来说似乎是一种“代码味道”。在您开始使用它之前,请评估您是否真的需要它。

【讨论】:

  • FWIW,我不确定这实际上是否回答了这个问题。我不知道智能感知是否能够闻出这个味道。 (似乎不太可能)。但是如果你要动态导入,这就是这样做的方法(使用__import__ 而不是exec)。
  • 是的,您的代码似乎更简洁。我也不确定。我相信这就是为什么 web2py 对智能感知不友好的原因。为了更好的理由,我为什么要这样做,我已经编辑了我的答案。谢谢
  • 可惜getattr好像只对class有效,对module无效,inspect.get_members()好像太多了
  • 动态导入很有用,当您拥有一堆模块并且不想加载所有内容时。
【解决方案2】:

好的,这是您的解决方案:

使用名称作为字符串导入模块:

__import__(modulename, globals(), locals(), ['*'])

从模块路径加载类:

cls = getattr(sys.modules[modulename], classname)

目录结构:

:/tmp/dynamic_import:~ ls
chocolate   fruit       need_dynamic.py
:/tmp/dynamic_import:~

need_dynamic.py

fruit
  |-- strawberry.py
  |-- orange.py

chocolate
 |-- cadbury.py
 |-- kitkat.py

这里是fruit里面的一个模块,我根据模块名的首字母大写来命名类名:

:/tmp/dynamic_import:~ cat orange.py
class Orange(object):
    def say(self):
        return "Say cheese from class: %s" % __name__
:/tmp/dynamic_import:~

这是您的主要脚本:

#!/usr/bin/env python

import os
import sys
import inspect

def load_modules_from_path(path):
    """
    Import all modules from the given directory
    """
    # Check and fix the path
    if path[-1:] != '/':
        path += '/'

    # Get a list of files in the directory, if the directory exists
    if not os.path.exists(path):
         raise OSError("Directory does not exist: %s" % path)

    # Add path to the system path
    sys.path.append(path)
    # Load all the files in path
    for f in os.listdir(path):
        # Ignore anything that isn't a .py file
        if len(f) > 3 and f[-3:] == '.py':
            modname = f[:-3]
            # Import the module
            __import__(modname, globals(), locals(), ['*'])

def load_class_from_name(fqcn):

    # fqcn = fully qualified classname
    # Break apart fqcn to get module and classname

    paths = fqcn.split('.')
    modulename = '.'.join(paths[:-1])
    classname = paths[-1]

    # Import the module
    __import__(modulename, globals(), locals(), ['*'])

    # Get the class
    cls = getattr(sys.modules[modulename], classname)
    # Check cls
    if not inspect.isclass(cls):
        raise TypeError("%s is not a class" % fqcn)

    # Return class
    return cls

def main():

    food_list = ['fruit', 'chocolate']
    subfood_list = [['strawberry', 'orange'],['cadbury', 'kitkat']]

    # show food list for users to select.
    for i in xrange(len(food_list)):
        print '%d: %s' % (i, food_list[i])

    food_index = int(raw_input('Choose one: '))

    for i in xrange(len(subfood_list[food_index])):
        print '%d: %s' % (i, subfood_list[food_index][i])

    subfood_index = int(raw_input('Chose one: '))
    pkg = food_list[food_index]
    module = subfood_list[food_index][subfood_index]
    class_name = module.title()

    load_modules_from_path(pkg)
    new_class = load_class_from_name('%s.%s' % (module, class_name))

    # instantiation
    obj = new_class()
    print obj.say()

if __name__ == '__main__': main()

这是输出:

:/tmp/dynamic_import:~ python need_dynamic.py
0: fruit
1: chocolate
Choose one: 0
0: strawberry
1: orange
Chose one: 0
Say cheese from class: strawberry
:/tmp/dynamic_import:~ python need_dynamic.py
0: fruit
1: chocolate
Choose one: 1
0: cadbury
1: kitkat
Chose one: 0
Say cheese from class: cadbury
:/tmp/dynamic_import:~

如果可行,请告诉我。

【讨论】:

  • 好吧,cls = getattr(sys.modules[modulename], classname) 做这个把戏
  • 即使我有一个大的监控脚本,我会根据硬件类型加载模块和类,所以我不需要每次都将一堆模块加载到我的主脚本中。
猜你喜欢
  • 1970-01-01
  • 2021-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-10
  • 2016-09-01
  • 1970-01-01
相关资源
最近更新 更多