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

5

6

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

Ornamentation answered 15/11, 2009 at 20:19 Comment(0)
D
9

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]
Distant answered 15/11, 2009 at 20:24 Comment(1)
Ahhh! Brilliant! :-) I need to think more out of the box, I think!Ornamentation
V
4

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

Verbify answered 15/11, 2009 at 20:50 Comment(1)
Excellent, thanks for the answer. I'll take some time to wrap my head around this, but I can see how useful that will be.Ornamentation
N
3

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.

Nicotiana answered 15/11, 2009 at 20:48 Comment(1)
Does that first code section have an unbalanced right paren? Thanks the answer, all of this will come in useful!Ornamentation
B
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,

Berga answered 19/11, 2009 at 5:51 Comment(0)
C
1
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.

Cinda answered 15/11, 2009 at 22:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.