Mittwoch, 27. Juni 2012

Python 004: My first closure

Depending on the presence of a number of files, a couple of functions had to be called. But depending on the kind of file, a different function was required. However, for each file, the logic to determine if the corresponding function should be called or not is the same. For technical reasons, the respective functions are in different modules (executed through some Ajax).
So it seemed to me I would have to write the logic part specifically for each case, i.e. for each case where a file is available or not.
Something as below:

if file_exists('-ini'):
    function_one()
if not file_exists('-ini'):
    generate_ini_file()
    function_one()
if file_exits('-fin'):
    function_two()
if not file_exists('-fin'):
    generate_fin_file()
    function_two()

and so on. However, I remembered, in Python everything is an object. Even functions. So, why not prepare a function, which takes care of the logic and the execution of the function which I want to apply to each of the corresponding files. But then, where should the applied function come from? Exactly, from a closure. This basically is a function which generates functions at runtime. What kind of arguments does it take? Precicely, the function you actually want to call. Huh? Yes, of course the applied function needs to be defined somewhere, but thats another story, lets assume we have it. It could look something like this:

def translate_com(target):
    """Calling VMD and returning the output directly."""
    vmd_src = 'mol load pdb %s-fix.pdb\n' % (bio_lib.pdb_base_path + target)\
            + 'set HOME ' + bio_lib.vmd_base_path + '\n'\
            + 'molinfo 0 get center\n'\
            + 'env\n'
    comp = Popen([bio_lib.vmd_cmd_path, '-dispdev', 'text', '-eofexit'],
                  stdout=PIPE, stdin=PIPE, stderr=PIPE, shell=False)
    comp.stdin.write(vmd_src)
    vmd_com_result = comp.communicate()[0]

    for line in vmd_com_result.split('\n'):
        if len(line) > 0 and line[0] == '{':
            com_xyz = [float(i) for i in line[1:-1].split()]
    com_xyz = [i*(-1) for i in com_xyz] 
    # Calling the VMD reorientation script.
    reo_src = bio_lib.get_reo_src(com_xyz, target)
    vmdp = Popen([bio_lib.vmd_cmd_path, '-dispdev', 'text', '-eofexit'],
                  stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=False)
    vmdp.stdin.write(reo_src)
    print vmdp.communicate()[0]

It does something, other functions are doing similar things. This is, e.g., the 'function_two' above. So for each of these, there would be a logic block as this:

def check_availability_and_translate(target):
    # Use upload.
    if uploaded:
        # Force overwrite.
        if overwrite:
            translate_com(target)
        # Does not exist.
        if not os.path.exists(bio_lib.pdb_base_path + target + '-reo.pdb'):
            translate_com(target)
    # Download.
    else:
        # Use existing.
        if os.path.exists(bio_lib.pdb_base_path + target + '-reo.pdb'):
            print "Structure available." 
        # Fixed structure not available.
        else:
            reo.translate_com(target)

In the above, there's also a bit of user interaction coming in, but that's not the point. The point is, all this is the same for each of my functions. So, lets generate a function that returns this logic, but takes the 'translate_com' function as an argument and calls it whenever *a* function is called:

def closure(state):
    """
        'state': '-ini', '-fin', '-reo'
    """
    def check_availability_and_apply(target, uploaded, overwrite, func):
        # Use upload.
        if uploaded:
            print "Uploaded"
            # Force overwrite.
            if overwrite:
                print "Overwriting"
                func(target)
            # Does not exist.
            if not os.path.exists(pdb_base_path + target + state):
                print "Not found, fixing."
                func(target)
            # Use existing.
        # Download.
        else:
            # Use existing.
            if os.path.exists(pdb_base_path + target + state):
                print "Structure available."
            # Structure not available.
            else:
                func(target)
    return check_availability_and_apply

The fun is in the last line. As one can see, a function is returned which was built within the closure. So how can I now call (or maybe better "apply") the 'translate_com' function?

closure('-ini.pdb')(target, uploaded, overwrite, translate_com)


And how about another function?

closure('-fix.pdb')(target, uploaded, overwrite, fix_pdb)


This is really cool, because now file checks are in one place and application is in a separate place. So only one place to edit.
I would really like to hear my boss asking me now: "So how many lines of code did you write yesterday?" Then I could answer: "Well, actually like -200."

Keine Kommentare:

Kommentar veröffentlichen