2014-02-01 2 views
2

Хорошо, это немного сложно.Сделайте динамический импорт, который все еще явственен в Python

Скажем, у меня есть модуль внутри упаковки:

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) не понимает мой код, и не может сделать IntelliSense (автоматическое завершение кода).

Если я использую первый подход, то IntelliSense, очевидно, работает:

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 

Но если я использую второй подход, то IntelliSense не может сделать завершение для меня:

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

Я знаю, , это компромисс между динамическим импортом в Python. Но я хочу знать, есть ли какая-то альтернатива, позволяющая мне сделать второй подход (возможно, не использовать exec), который все еще может позволить intellisense общей IDE (например, Pydev) угадывать метод внутри класса?

EDIT: Зачем мне это нужно? Скажем, у меня есть такая структура каталогов

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_list и subfood_list автоматически получать каталог.

Представьте, что у вас есть структура классификации данных и вы хотите, чтобы пользователь выбирал, какой метод они хотят использовать. Пользователь также должен иметь возможность расширить структуру, просто добавив пакет python к модулю.

Надеюсь, этот пример является разумным.

изменить еще раз принятый ответ не решает проблему intellisense. Но он показывает, как лучше кодировать. Я думаю, что это проблема IDE, а не проблема python. Я отправлю еще один вопрос.

+1

Как ваш метод лучше, чем делать 'из package.module import klass'? Последнее гораздо более семантическое и не путает других программистов, которые смотрят на ваш код. –

+0

Как я уже сказал, мой подход легче писать. Это также позволяет мне называть разные методы и классы с помощью переменной конфигурации. – goFrendiAsgard

+0

Не пытайтесь быть умными. – hop

ответ

0

Ok вот ваше решение:

Для Импортирование модуля, используя имя в виде строки:

__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 

Вот один из модуля внутри плода, я назвать имя класса по отношению к модулю имя с инициалами колпачков:

:/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:~ 

Пожалуйста, дайте мне знать, если это работает.

+0

Хорошо, 'cls = getattr (sys.modules [modulename], classname)' do the trick – goFrendiAsgard

+1

Даже у меня большой скрипт мониторинга, который я загружаю модуль и класс на основе аппаратного типа, поэтому мне не нужно загружать кучу модулей в мой основной скрипт каждый раз. –

1

Вы хотели бы использовать __import__: встроенную команду

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

Конечно, вы можете бросить обработку вашей ошибки назад там, если вы хотите, и если честно, я не совсем уверен, почему вы бы хотели сделать это в первую очередь. Динамическое импортирование кажется мне «запахом кода» для самых вещей. Оцените, действительно ли вам нужно, прежде чем вы уйдете и начнете его использовать.

+1

FWIW, я не уверен, что это * на самом деле * отвечает на вопрос. Я не знаю, сможет ли intellisense почувствовать запах этого или нет. (кажется маловероятным). Но если вы собираетесь импортировать динамически, это способ сделать это (используя '__import__' вместо' exec'). – mgilson

+0

Да, ваш код кажется чище. Я тоже не уверен. Я считаю, что именно поэтому web2py не дружит с intellisense. И для лучшей причины, почему я хочу это сделать, я отредактировал свой ответ. Thx – goFrendiAsgard

+0

К сожалению, похоже, что 'getattr' работает только для класса, а не для модуля, а' inspect.get_members() 'кажется слишком большим. – goFrendiAsgard

Смежные вопросы