Python: конвертировать строку в имя функции; getattr или равно? - PullRequest
4 голосов
/ 15 ноября 2009

Я редактирую PROSS.py для работы с файлами .cif для структур белка. Внутри существующего PROSS.py есть следующие функции (я считаю, что это правильное имя, если оно не связано ни с одним классом?), Просто существующее в файле .py:

...
def unpack_pdb_line(line, ATOF=_atof, ATOI=_atoi, STRIP=string.strip):
...
...
def read_pdb(f, as_protein=0, as_rna=0, as_dna=0, all_models=0,
    unpack=unpack_pdb_line, atom_build=atom_build):

Я добавляю синтаксический анализатор оптонов для аргументов командной строки, и один из вариантов - указать альтернативный метод для использования, кроме unpack_pdb_line. Таким образом, подходящая часть парсера параметров:

...
parser.add_option("--un", dest="unpack_method", default="unpack_pdb_line", type="string", help="Unpack method to use. Default is unpack_pdb_line.")
...
unpack=options.unpack_method

Однако options.unpack_method - это строка, и мне нужно использовать функцию с тем же именем, что и строка внутри options.unpack_method. Как использовать getattr и т. Д. Для преобразования строки в фактическое имя функции?

Спасибо

Пол

Ответы [ 5 ]

8 голосов
/ 15 ноября 2009

Обычно вы просто используете dict и сохраняете (func_name, function) пар:

unpack_options = { 'unpack_pdb_line' : unpack_pdb_line,
                   'some_other' : some_other_function }

unpack_function = unpack_options[options.unpack_method]
4 голосов
/ 15 ноября 2009

Если вы хотите использовать словари (& c), которые Python уже хранит от вашего имени, я бы предложил:

def str2fun(astr):
  module, _, function = astr.rpartition('.')
  if module:
    __import__(module)
    mod = sys.modules[module]
  else:
    mod = sys.modules['__main__']  # or whatever's the "default module"
  return getattr(mod, function)

Возможно, вы захотите проверить сигнатуру функции (и перехватить исключения, чтобы получить более приятные сообщения об ошибках), например. через inspect, но это полезная функция общего назначения. Легко добавить словарь горячих клавиш, как запасной вариант, если некоторые известные функции полные имена строк (включая квалификации модулей / пакетов) неуместны для такого выражения.

Обратите внимание, что мы не используем результат __import__ (он не работает правильно, когда функция находится в модуле внутри некоторого пакета, так как __import__ возвращает имя пакета верхнего уровня ... просто доступ к sys.modules после импорта более практичен).

2 голосов
/ 19 ноября 2009

vars()["unpack_pdb_line"]() тоже будет работать.

или

globals () или localals () также будут работать аналогичным образом.

>>> def a():return 1
>>>
>>> vars()["a"]
<function a at 0x009D1230>
>>>
>>> vars()["a"]()
1
>>> locals()["a"]()
1
>>> globals()["a"]()
1

Приветствия

1 голос
/ 16 ноября 2009
function = eval_dottedname(name if '.' in name else "%s.%s" % (__name__, name))

Где eval_dottedname():

def eval_dottedname(dottedname):
    """
    >>> eval_dottedname("os.path.join") #doctest: +ELLIPSIS
    <function join at 0x...>
    >>> eval_dottedname("sys.exit") #doctest: +ELLIPSIS
    <built-in function exit>
    >>> eval_dottedname("sys") #doctest: +ELLIPSIS
    <module 'sys' (built-in)>
    """
    return reduce(getattr, dottedname.split(".")[1:],
                  __import__(dottedname.partition(".")[0]))

eval_dottedname() - единственный из всех ответов, который поддерживает произвольные имена с несколькими точками в них, например, datetime.datetime.now. Хотя это не работает для вложенных модулей, которые требуют импорта, но я даже не могу вспомнить пример из stdlib для такого модуля.

1 голос
/ 15 ноября 2009

Если вы принимаете ввод от пользователя, в целях безопасности, вероятно, лучше всего используйте ручной диктовку, которая будет принимать только четко определенный набор допустимых пользовательских данных:

unpack_options = { 'unpack_pdb_line' : unpack_pdb_line,
    'unpack_pdb_line2' : unpack_pdb_line2,
    } 

Пренебрегая безопасностью на мгновение, давайте мимоходом отметим, что легкий способ перейти от (строки имен переменных) к (значение, на которое ссылается имя переменной) это использовать встроенный в глобальный ()) dict:

unpack_function=globals()['unpack_pdb_line']

Конечно, это будет работать, только если переменная unpack_pdb_line находится в глобальном пространстве имен.

Если вам нужен пакет для модуля или модуля для переменной, то Вы могли бы использовать эту функцию

import sys
def str_to_obj(astr):
    print('processing %s'%astr)
    try:
        return globals()[astr]
    except KeyError:
        try:
            __import__(astr)
            mod=sys.modules[astr]
            return mod
        except ImportError:
            module,_,basename=astr.rpartition('.')
            if module:
                mod=str_to_obj(module)
                return getattr(mod,basename)
            else:
                raise

Вы можете использовать это так:

str_to_obj('scipy.stats')
# <module 'scipy.stats' from '/usr/lib/python2.6/dist-packages/scipy/stats/__init__.pyc'>

str_to_obj('scipy.stats.stats')
# <module 'scipy.stats.stats' from '/usr/lib/python2.6/dist-packages/scipy/stats/stats.pyc'>

str_to_obj('scipy.stats.stats.chisquare')
# <function chisquare at 0xa806844>

Работает для вложенных пакетов, модулей, функций или (глобальных) переменных.

...