2
\$\begingroup\$

Originally asked this on SO but was redirected here.

I have a script that's asking the user to input a lot of dates, mostly in functions similar to the one below. These need to account for invalid date formats:

import numpy as np
import pandas as pd
import xlwings as xw
import datetime as dtt

def week_init():
        """Creates an initial week datetime and determines what the checking cutoff
        'beforeday' to be used in Stage 2 is."""
        week = input('Week to check: MM/DD/YYYY\n')
        switch = 1
        while switch == 1:
            try:
                week = dtt.datetime.strptime(week,'%m/%d/%Y') #turns input to a datetime
                switch = 0
            except ValueError: #couldn't parse as a date MM/DD/YYYY
                week = input('Unrecognized date format, please try again: MM/DD/YYYY\n')
        beforeday = (input('Check days before date (Press enter to use today): MM/DD/YYYY\n')
            or dtt.date.today())
        if (beforeday != dtt.date.today()):
            switch = 1
            while switch == 1:
                try:
                    beforeday = dtt.datetime.strptime(beforeday,'%m/%d/%Y')
                    switch = 0
                except ValueError: #couldn't parse as a date MM/DD/YYYY
                    beforeday = input('Unrecognized date format, please try again: MM/DD/YYYY\n')
        return week, beforeday

There are also functions that check an index for a given date, and have to deal with a given date not matching any of the indices:

def spotmap(week, bam, pt):
    """Maps the pivoted data for the chosen week to the BAM dataframe's SD column."""
    print('Mapping data to BAM... ',end=''),
    switch = 1
    while switch == 1:
        try:
            bam['SD'] = bam.index.to_series().map(pt.fillna(0)['len',week])
            switch = 0
        except KeyError: #date doesn't match any of the pivot table's columns
            print('Invalid date, please try again.')
            week = input('Week start date (Sunday): MM/DD/YYYY\n')
    print('Done')
    return bam

As the script has a lot of things to do after it gets these dates, I don't want it to crash when it has a problem, but it can't proceed without correct date inputs so I currently have it looping with those "switch" variables controlling when it is willing to move on with a valid date. As you can see, though, these try/except blocks are rapidly bloating the otherwise straightforward code. Is there a way to condense these things? Also, a better method than the switches?

https://stackoverflow.com/questions/9386592/repetitive-try-and-except-clauses

The answers here suggested decorators, but as far as I can tell from the documentation and pages like this, those are for wrapping functions, not for replacing interior code blocks. Also I'm not sure how useful they'd be, because the try/except blocks are mostly unique in what they're trying to accomplish (ie, which variable they're changing or whether they're checking against an index). I guess I'm really just hoping for some better general exception-handling syntax.

\$\endgroup\$
  • \$\begingroup\$ Note that while true: with break is much more pythonic than setting a flag like switch. Also see the canonical stackoverflow.com/q/23294658/3001761 \$\endgroup\$ – jonrsharpe Jun 27 '17 at 18:23
  • \$\begingroup\$ As far as I can see, you're not using numpy, pandas and xlwings in the code snippet, so you could omit them. \$\endgroup\$ – Attilio Jun 27 '17 at 19:24
  • \$\begingroup\$ Welcome to Code Review! Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers. \$\endgroup\$ – Peilonrayz Jun 28 '17 at 15:48
2
\$\begingroup\$

I will focus on the main question, i.e. how to decompose the code so that checks are kept, but the code does not get bloated with them.

Regarding the first part: I recommend to factor out the code asking for input, and split the initialization function into two separate ones. Something like this:

import datetime as dtt

def read_date(prompt, def_date=None):
          switch = 1
          while switch == 1:
                week = input(prompt) or def_date
                try:
                       week = dtt.datetime.strptime(week,'%m/%d/%Y')
                       switch = 0
                except ValueError:
                       print('Unrecognized date format, please try again\n')
          return week

def init_week():
    return read_date('Week to check: MM/DD/YYYY\n')

def init_before_day():
    return read_date('Check days before date (Press enter to use today): MM/DD/YYYY\n', '{0:%m/%d/%Y}'.format(dtt.date.today()))

def week_init():
    return [init_week(), init_before_day()]
    # alternatively (without the helpers): return [read_date(), read_date(dtt.date.today())]

For the second part, I suggest factoring out the mapping logic from inputting the date (or at least, the date should also be read in the function itself -- but receiving it as a parameter, and then reading it again is very messy IMHO). So this would look something like the following:

def spotmap_impl(week, bam, pt):
    bam['SD'] = bam.index.to_series().map(pt.fillna(0)['len',week])
    return bam

def spotmap(bam, pt):
    switch = 1
    while switch == 1:
        try:
            week = input('Week start date (Sunday): MM/DD/YYYY\n')
            bam = spotmap_impl(week, bam, pt)
            switch = 0
        except KeyError:
            print('Invalid date, please try again.')
    return bam;

Remarks:

  • I tried to leave the original names as much as possible, but I feel they could be improved (e.g. in read_date we are reading a date, which is then somehow used as a week; so I would call the internal variable something like input_date. Also instead of switch, I would use a name like success, and values True/False, instead of 1 and 0).
  • I could not test the second part, as I do not understand exactly what is the meaning and contents of bam and pt, but I hope you get the idea :) (Btw., these variable names might be improved too.)
  • You might want to add further validation code to the parameters of spotmap_impl. And then write unit tests for it, if you haven't done so yet ;)
\$\endgroup\$
  • \$\begingroup\$ Thanks; I've applied this function encapsulation to the functions and appended it to the original question. \$\endgroup\$ – Steve R Jun 28 '17 at 15:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.