views:

275

answers:

5

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

+7  A: 

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]
THC4k
Ahhh! Brilliant! :-) I need to think more out of the box, I think!
TallPaul
+1 using a dispatch dict is easier and clearer.
nosklo
+1  A: 

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.

unutbu
Does that first code section have an unbalanced right paren? Thanks the answer, all of this will come in useful!
TallPaul
Yes, TallPaul. (Thanks for correcting the typo, voyager!)
unutbu
+2  A: 

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

Alex Martelli
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.
TallPaul
@TallPaul, you're welcome -- and, sure, take your time -- do accept **some** answer eventually (not just to this question, but to most of those you've asked!!!), otherwise your 0% acceptance rate will become a strong turn-off towards bothering to answer your Qs!-)
Alex Martelli
+1  A: 
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.

J.F. Sebastian
+1  A: 

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,

S.Mark