I have a bunch of photos in a folder with various EXIF data, and I'd like to output various parts of it to Excel.  I'm learning Python (currently using 2.7) and thought this would be a fun task for me to try out, as it incorporates functions, loops, and two libraries (I'm using PIL and Openpxyl).The code currently works fine! I'm able to get data for about 650 images in under three seconds.  Mainly, I'm trying to learn how to better structure the project.  My main concerns are with how I'm calling my functions. For example, right now, I want to get the Latitude, Longitude, and DateTime the photo was taken. But say I add another function (i.e. get_Exposure()), I'd like to see if I can better write the writeToFile() function to handle that.  Coming from a VBA background, I'm thinking I could loop a single line like ws1.cell(column=[first variable], row=row, value=[first variable value]) somehow.Finally, am I calling all of these functions properly? The whole declaring of variables before the for root, dirs, ... line seems out of place to me for some reason.  (FWIW, I am mainly aquainted with VBA, so my thinking is all coming from how one does things in that...)from PIL import Imagefrom PIL.ExifTags import TAGS, GPSTAGSimport os, sysfrom openpyxl import Workbookfrom openpyxl.compat import rangefrom openpyxl.utils import get_column_letterdef _get_if_exist(data, key):    if key in data:        return data[key]    return Nonedef get_exif_data(fn):    Returns a dictionary from the exif data of an PIL Image item. Also converts the GPS Tags    image = Image.open(fn)    exif_data = {}    info = image._getexif()    if info:        for tag, value in info.items():            decoded = TAGS.get(tag, tag)            if decoded == GPSInfo:                gps_data = {}                for t in value:                    sub_decoded = GPSTAGS.get(t, t)                    gps_data[sub_decoded] = value[t]                exif_data[decoded] = gps_data            else:                exif_data[decoded] = value    return exif_datadef _convert_to_degrees(value):    Helper function to convert the GPS coordinates stored in the EXIF to degrees in float format    d0 = value[0][0]    d1 = value[0][1]    d = float(d0) / float(d1)    m0 = value[1][0]    m1 = value[1][1]    m = float(m0) / float(m1)    s0 = value[2][0]    s1 = value[2][1]    s = float(s0) / float(s1)    return d + (m / 60.0) + (s / 3600.0)def get_time_taken(exif_data):    timeTaken = None    if DateTimeOriginal in exif_data:        timeTaken = exif_data[DateTimeOriginal]    return timeTakendef get_lat(exif_data):    lat = None    if GPSInfo in exif_data:        gps_info = exif_data[GPSInfo]        gps_latitude = _get_if_exist(gps_info, GPSLatitude)        gps_latitude_ref = _get_if_exist(gps_info, 'GPSLatitudeRef')        if gps_latitude and gps_latitude_ref:            lat = _convert_to_degrees(gps_latitude)            if gps_latitude_ref != N:                lat = 0 - lat    return latdef get_lon(exif_data):    lon = None    if GPSInfo in exif_data:        gps_info = exif_data[GPSInfo]        gps_longitude = _get_if_exist(gps_info,GPSLongitude)        gps_longitude_ref = _get_if_exist(gps_info, 'GPSLongitudeRef')        if gps_longitude and gps_longitude_ref:            lon = _convert_to_degrees(gps_longitude)            if gps_longitude_ref != E:                lon - 0 - lon    return londef writeToFile(imageName, lat, lon, row, timeTaken, ws1):    ws1.cell(column=1, row=row, value=imageName)    ws1.cell(column=2, row=row, value=lat)    ws1.cell(column=3,row=row, value=lon)    ws1.cell(column=4, row=row,value=timeTaken)def saveFile(wb, xlFile):    wb.save(filename = xlFile)row = 1wb = Workbook()ws1 = wb.activews1.title = GPS CoordsxlFile = D:\\myUser\\Pictures\\Digital Pictures\\GPSCoords.xlsxfor root, dirs, filenames in os.walk(D:\\myUser\\Pictures\\Digital Pictures\\):    for imageName in filenames:        if imageName[-4:] == .jpg:            fn = D:\\myUser\\Pictures\\Digital Pictures\\ + imageName            exif_data = get_exif_data(fn)            get_exif_data(fn)            lat = str(get_lat(exif_data))            lon = str(get_lon(exif_data))            timeTaken = str(get_time_taken(exif_data))            print imageName + :  + lat + ,  + lon + ;  + timeTaken            writeToFile(imageName, lat, lon, row, timeTaken, ws1)            row += 1saveFile(wb, xlFile)
Write various EXIF data to Excel Sheet
python;excel;image
I would change from python 2 to Python 3. There are so many good changes in Python 3, among which for this problem unicode handling is most important that it's worth it. for the exif-data, PILLOW should be a simple replacement for PILgeneral remarksdef _get_if_exist(data, key)python dicts have get() method with a default argument. Instead of calling making your own function, you can easily do d.get(key, None)Seperation of functionsNow you loop over the file, check if it is an image and process it in 1 loop. I suggest using 1 function to find all images, a second function to extract all exif-information, a third function to extract the important information, and then a function to bring it all togetherMy attemptfind all imagesdef find_images(image_dir, extensions=None):    default_extensions = ('jpg', 'jpeg')    if extensions is None:        extensions = default_extensions    elif isinstance(extensions, str):        extensions = (extensions,)    for root, dirs, filenames in os.walk(image_dir):        for filename in filenames:#             print(filename, filename.split('.', 1))            if filename.split('.', 1)[-1].lower() in extensions:                yield os.path.join(root, filename)takes a starting directory and a collection of extensions. It uses str.split('.') to get the extension, instead of the arbitrary [-4:]This is a generator, which yields the path to an image every iteration. You could make the output more sophisticated by yield filename, os.path.join(root, filename) or yielding a pathlib.Path instead of a strGetting all exif datadef process_exif_data(image):    decoded_exif = {}    with Image.open(image) as image_file:        exif_data = image_file._getexif()        if exif_data is None:            return None        for tag, value in exif_data.items():            decoded = TAGS.get(tag, tag)            if decoded == GPSInfo:                decoded_exif.update(decode_gps_data(value))            else:                decoded_exif[decoded] = value    # This could be done with a dict comprehension and a ternary expression too    return decoded_exifThis is pretty much your solution, only I put the GPSInfo into the dict with all exif-info, instead of nested a level deeper. I also do the processing of the GPS-data here already instead of later onprocess the GPS-datadef decode_gps_data(info):    gps_tags = {GPSTAGS.get(k, k): v for k, v in value.items}    lat, long = get_coordinates(gps_tags)    gps_tags['lat'] = lat    gps_tags['lon'] = lon    return gps_tagsThis should speak for itself.get the coordinatesdef get_coordinates(gps_tags):coords = {'Latitude': 'N', 'Longitude': 'E'}for coord, nominal_ref in coords.items():    c = gps_tags.get(GPS%s % coord, None)    c_ref = gps_tags.get(GPS%sRef % coord, None)    if c and c_ref:        yield _convert_to_degrees(c, c_ref, nominal_ref)    else:        yield Nonethe code to get the latitude and the longitude is the same. The only difference is the nominal reference ('N' or 'E') and the tag, so I abstracted this.def _convert_to_degrees(value, ref, nominal_ref=None:    if nominal_ref is None:        nominal_ref = ('N', 'E',)    elif isinstance(nom, str):        nominal_ref = (nominal_ref, )    ref = 1 if ref in nominal_ref else -1    return ref * sum(float(v[0]) / float(v[1]) / 60 ** i for i, v in enumerate(value))Instead of O - calculated_degrees like you do, I multiply by 1 or -1 depending on the reference. The calculation itself uses tuple unpacking and enumerate to do the actual calculation. Since I don't have images with, I have no data to check it with, but it should do the same as your get_lat and get_lon.Extract the importand datadef extract_important_data(image_data, important_datalabels=('lat', 'lon', 'DateTimeOriginal')):    if image_data is None:        return None    return {key: image_data.get(key, None) for key in important_datalabels}This just returns a selection of the dict of all exif_data. You can specify which tags are important to you, so you can easily expand the needed information laterBringing it togetherimport PILfrom PIL import Imagefrom PIL.ExifTags import TAGS, GPSTAGSimport pandas as pdimport osThe imports. I use pandas instead of openpyxl directly, since that allows me a lot more freedom to do smaller data processing afterwardsdef extract_info(images, important_datalabels=('lat', 'lon', 'DateTimeOriginal')):    for image_path in images:        exif_data = process_exif_data(image_path)        yield image_path, extract_important_data(exif_data, important_datalabels=important_datalabels)This just iterates over all images thrown at it, and yields the image and the important data in the exifIf you don't want to include the images without EXIF-info in your final results, you can do it like thisdef extract_info(images, important_datalabels=('lat', 'lon', 'DateTimeOriginal')):    for image_path in images:        exif_data = process_exif_data(image_path)        important_data = extract_important_data(exif_data)        if important_data:            yield image_path, important_datamain()def main(image_dir=None, filename=None, important_datalabels=('lat', 'lon', 'DateTimeOriginal')):    if image_dir is None:        image_dir='.'    images = find_images(image_dir)    info = extract_info(images, important_datalabels=important_datalabels)    result_df = pd.DataFrame(columns = important_datalabels)    for image_path, item in info:        result_df.loc[image_path] = item    if 'DateTimeOriginal' in important_datalabels:        date_format = '%Y:%m:%d %H:%M:%S'        result_df['DateTimeOriginal'] = pd.to_datetime(result_df['DateTimeOriginal'], format=date_format)    if filename:        result_df.to_excel(filename)    return result_dfThis is the method that really ties everything together.It looks for all the images in image_dir, if no extensions are passed on, takes the default extensions in that methodextracts the important info from those imagesmakes an empty pandas.DataFrame with the important datalabels as columnsstarts filling this DataFramechanges the date to a datetime.datetime objectif a filename is passed on, writes the DataFrame to this filenameresultFor me this yielded     lat     lon     DateTimeOriginal.\data\images\image-13.jpg  NaN     NaN     NaT.\data\images\piazza-nite-2-big.jpg     None    None    2006-06-07 22:53:09