Python: Convert string into function name; getattr or equal?

≯℡__Kan透↙ 提交于 2019-12-07 08:59:08

问题


I am editing PROSS.py to work with .cif files for protein structures. Inside the existing PROSS.py, there is the following functions (I believe that's the correct name if it's not associated with any class?), just existing within the .py file:

...
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):

I am adding an optons parser for command line arguments, and one of the options is to specify an alternate method to use besides unpack_pdb_line. So the pertinent part of the options parser is:

...
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

However, options.unpack_method is a string and I need to use the function with the same name as the string inside options.unpack_method. How do I use getattr etc to convert the string into the actual function name?

Thanks,

Paul


回答1:


Usually you just use a dict and store (func_name, function) pairs:

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

unpack_function = unpack_options[options.unpack_method]



回答2:


If you want to exploit the dictionaries (&c) that Python's already keeping on your behalf, I'd suggest:

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)

You'll probably want to check the function's signature (and catch exceptions to provide nicer error messages) e.g. via inspect, but this is a useful general-purpose function. It's easy to add a dictionary of shortcuts, as a fallback, if some known functions full string names (including module/package qualifications) are unwieldy to express this way.

Note we don't use __import__'s result (it doesn't work right when the function is in a module inside some package, as __import__ returns the top-level name of the package... just accessing sys.modules after the import is more practical).




回答3:


vars()["unpack_pdb_line"]() will work too.

or

globals() or locals() will also work similar way.

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

Cheers,




回答4:


If you are taking input from a user, for the sake of security it is probably best to use a hand-made dict which will accept only a well-defined set of admissible user inputs:

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

Ignoring security for a moment, let us note in passing that an easy way to go from (strings of variable names) to (the value referenced by the variable name) is to use the globals() builtin dict:

unpack_function=globals()['unpack_pdb_line']

Of course, that will only work if the variable unpack_pdb_line is in the global namespace.

If you need to reach into a packgae for a module, or a module for a variable, then you could use this function

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

You could use it like this:

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>

It works for nested packages, modules, functions, or (global) variables.




回答5:


function = eval_dottedname(name if '.' in name else "%s.%s" % (__name__, name))

Where 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() is the only one among all answers that supports arbitrary names with multiple dots in them e.g., `'datetime.datetime.now'. Though it doesn't work for nested modules that require import, but I can't even remember an example from stdlib for such module.



来源:https://stackoverflow.com/questions/1738687/python-convert-string-into-function-name-getattr-or-equal

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!