Keyboard input with timeout in Python

From StackOverflow: http://stackoverflow.com/questions/1335507/keyboard-input-with-timeout-in-python

This seems to be something folks want to do.  I recently had an interest to launch a script every morning to have me check in with some goal, and was wondering how I could do this, i.e.

At 7:00 AM, fire up a script with some prompt and input request, something like:

raw_input("Time to log the daily weight-in! How much today? ")

But… suppose I slept in today.  The prompt just sits there, blocking… and suppose the script that launches this inquiry now fires up a new instance of the program… will that prompt and wait, too?  Ugh.

Better to have something that operates like “oh look, there’s been no answer for the past 30 minutes, maybe he forgot or slept in?” and then concludes “okay, log that there was no answer for today, and exit.”  Simple, and no more stacked up instances.

Problem is, there’s apparently no read with timeout in Python. Or at least, not one that works, cross-platform.  (There are some solutions with Linux signals.)

I found the following code at the above StackOverflow post mentioned above.  It was downvoted, but I actually found this approach interesting, a kind of “out of the box” thinking approach to the problem.  Here, a timer is set up.  And what you do is indicate, via Ctrl-C, when you’re at the keyboard and ready to begin input.  If the interrupt is never received?  Then it’s a timeout.

Sure, it’s a little quirky, but it can get a job done.

Two things I discovered or realized:

  1. Of course, if you do start typing, and then walk away, it will block just like the original case.
  2. I tried changing the sleep loop to just a single sleep statement.  But guess what — my Ctrl-C would not interrupt the sleep statement! (I”m on a Mac, fyi.)  If you have a loop around the sleep, it can detect the interrupt in-between the sleep statement.
    (I suppose I should head to the documentation to read up on this and verify my theory.)
  3. If you press Ctrl-C, you may have a perceptible momentary wait, while the “sleep(1)” completes.

P.S. Here is the exact code by “try” from the StackOverflow post.

from time import sleep

print('Please provide input in 20 seconds! (Hit Ctrl-C to start)')
try:
    for i in range(0,20):
        sleep(1) # could use a backward counter to be preeety :)
    print('No input is given.')
except KeyboardInterrupt:
    raw_input('Input x:')
    print('You, you! You know something.')

Here’s my slightly modified version, as a function (to make it easier to test in IDLE).

And sorry for the wrapping… i’m looking for a new template for the blog.


def prompt_with_timeout():
  from time import sleep

  print('Waiting... please press Ctrl-C when you wish to proceed.')
  try:
    for i in range(0, 30*60): # 30 minutes is 30*60 seconds
      sleep(1)
    print("Oops, sorry, no input was received today. Come back tomorrow.")
  except KeyboardInterrupt:
    weight_today = raw_input("Time to log the daily weight-in! How much today? ")
    print ("Thanks for logging: " + weight_today)
  return

Simple computational exercise

========
UPDATE 23-Jun-2013: After approving comments yesterday and tweaking my doctests, this exercise certainly must have been on my mind!  I woke up this morning with a “why didn’t I do it this way before?!” thought. My 2nd revision to this exercise is posted as a reply below.
========
UPDATE 10-Mar-2013: OUCH! Mongo apologies for all the kind people who have commented!!!! Just received another one today. I’m just a budding blogger (ha – more like blogger-wannabe; i don’t deserve the “blogger” title by any stretch — yet!) as well as a terrifically overworked employee, so there’s little time to play these days. I will be getting back into it with a small diversion toward into some HTML parsing.

Thank you, thank you, thank you to the commenters! It’s really delightful you happened to discover my little post. Cheers to all.

— Original 20-Jan-2013 post below —

Over the weekend, I was at the MIT Press bookstore in Cambridge MA for “The CSound Book” and of course I had to ask if they had any Python books.  I picked up a copy of John V. Guttag’s “Introduction to Computation and Programming Using Python“, Spring 2013 Edition.

Starting reading it, and will likely reveal various things from it as this blog continues.  For today, however, here’s the “finger exercise” from section 2.2:

“Write a program that examines three variables–x, y, and z–and prints the largest odd number among them.  If none of them are odd, it should print a message to that effect.”

Here’s my solution. It sure looks ugly to me, but I think it works.

I also added in the doctest module with verbose, as a kind of unit test, to validate against some outputs recorded in an IDLE session.   According to the documention, “The doctest module searches for pieces of text that look like interactive Python sessions, and then executes those sessions to verify that they work exactly as shown.”

Self-documenting AND testable — cool, yes?

POSTSCRIPT:  One can google and find an approach like this, but I took the path of a person who hasn’t yet learned anything other than simple comparison operators, compound Boolean operations, and int, print, and the modulo operator %.

OH, and to be clear. because I wanted to have the doctest module, the solution is also implemented with a function definition. Output from running is below the source code.

# largestodd.py
#
# Example 2.2 taken from "Introduction to Computation and
# Programming Using Python", Spring 2013 Edition, p.16
# Exercise author: John V. Guttag
#
# Code author: mgh from "My Pythonic Year" blog at pythonicyear.com

def largestodd(x,y,z):
    """Compare three numbers and print largest odd, 
       or none is odd, a message to that effect.

    >>> largestodd(1,3,5)
    z is largest odd
    >>> largestodd(1,5,3)
    y is largest odd
    >>> largestodd(7,5,3)
    x is largest odd
    >>> largestodd(2,5,3)
    y is largest odd
    >>> largestodd(2,5,7)
    z is largest odd
    >>> largestodd(5,2,7)
    z is largest odd
    >>> largestodd(7,2,5)
    x is largest odd
    >>> largestodd(7,9,2)
    y is largest odd
    >>> largestodd(11,9,2)
    x is largest odd
    >>> largestodd(11,2,2)
    x is largest odd
    >>> largestodd(2,3,2)
    y is largest odd
    >>> largestodd(2,2,23)
    z is largest odd
    >>> largestodd(2,2,2)
    NONE of x y z are odd

    """
    if x%2 != 0 :
        if y%2 != 0 :
            if z%2 !=0 :
            # all three are odd
                if x > y and x > z :
                    print 'x is largest odd'
                elif y > z:
                    print 'y is largest odd'
                else:
                    print 'z is largest odd'
            else: # only x and y are odd
                if x > y :
                    print 'x is largest odd'
                else:
                    print 'y is largest odd'
        elif z%2 != 0:
            # both x and z are odd
            if x > z :
                print 'x is largest odd'
            else:
                print 'z is largest odd'
        else:
            #x odd, but neither y nor z
            print 'x is largest odd'
    else:
        #x not odd, test y and z
        if y%2 != 0:
            # y is odd
            if z%2 != 0:
                # y and z are odd
                if y > z:
                        print 'y is largest odd'
                else:
                        print 'z is largest odd'
            else:
                print 'y is largest odd'
        else:
            #y not odd
            if z%2 != 0:
                print 'z is largest odd'
            else:
                print 'NONE of x y z are odd'
    return

if __name__ == "__main__":
        import doctest
        doctest.testmod()

Output when run:

>>> 
Trying:
    largestodd(1,3,5)
Expecting:
    z is largest odd
ok
Trying:
    largestodd(1,5,3)
Expecting:
    y is largest odd
ok
Trying:
    largestodd(7,5,3)
Expecting:
    x is largest odd
ok
Trying:
    largestodd(2,5,3)
Expecting:
    y is largest odd
ok
Trying:
    largestodd(2,5,7)
Expecting:
    z is largest odd
ok
Trying:
    largestodd(5,2,7)
Expecting:
    z is largest odd
ok
Trying:
    largestodd(7,2,5)
Expecting:
    x is largest odd
ok
Trying:
    largestodd(7,9,2)
Expecting:
    y is largest odd
ok
Trying:
    largestodd(11,9,2)
Expecting:
    x is largest odd
ok
Trying:
    largestodd(11,2,2)
Expecting:
    x is largest odd
ok
Trying:
    largestodd(2,3,2)
Expecting:
    y is largest odd
ok
Trying:
    largestodd(2,2,23)
Expecting:
    z is largest odd
ok
Trying:
    largestodd(2,2,2)
Expecting:
    NONE of x y z are odd
ok
1 items had no tests:
    __main__
1 items passed all tests:
  13 tests in __main__.largestodd
13 tests in 2 items.
13 passed and 0 failed.
Test passed.
>>>

Argument passing

Okay, starting to hit the tutorial for 2.7 over at python.org, and wanted to work out argument passing:

2.1.1. Argument Passing

When known to the interpreter, the script name and additional arguments thereafter are turned into a list of strings and assigned to the argvvariable in the sys module. You can access this list by executing import sys. The length of the list is at least one; when no script and no arguments are given, sys.argv[0] is an empty string. When the script name is given as '-' (meaning standard input), sys.argv[0] is set to '-'. When -c command is used, sys.argv[0] is set to '-c'. When -m module is used, sys.argv[0] is set to the full name of the located module. Options found after -c command or -m module are not consumed by the Python interpreter’s option processing but left in sys.argv for the command or module to handle.

Also – quick shout out to: how can i run my python program directly from the shell

Right.  What’s that all mean? Here’s my program, “test.py”:

#!/usr/bin/env python2.7
import sys
print "i am test.py and sys.argv[0] is: %s\n" \
      "and len(sys.argv) is: %d\n" \
      "and sys.argv is:" \
      %(sys.argv[0], len(sys.argv)), \
      sys.argv

Note, it’s executable:

PY$ ls -las test.py
8 -rwxr-xr-x 1 mgh wheel 129 Jan 19 22:52 test.py

And some results:

PY$ python test.py
i am test.py and sys.argv[0] is: test.py
and  len(sys.argv) is: 1
and sys.argv is: ['test.py']

PY$ # one arg
PY$ python test.py 1st
i am test.py and sys.argv[0] is: test.py
and len(sys.argv) is: 2
and sys.argv is: ['test.py', '1st']

PY$ # without python
PY$ ./test.py 1st 2nd
i am test.py and sys.argv[0] is: ./test.py
and len(sys.argv) is: 3
and sys.argv is: ['./test.py', '1st', '2nd']

PY$ ./test.py 1 2 'it works!'
i am test.py and sys.argv[0] is: ./test.py
and len(sys.argv) is: 4
and sys.argv is: ['./test.py', '1', '2', 'it works!']
PY$

OH SNAP — that’s cool. I just realized that “pythonic year” abbreviates to “PY”. Honestly, I did NOT plan THAT! Neato.

Okay, here’s a bit of an improvement using multi-line strings with triple-quote: ”’

test2py:

#!/usr/bin/env python2.7
import sys
print '''i am test.py 
     and sys.argv[0] is: %s
     and len(sys.argv) is: %d 
     and sys.argv is: %s
     '''%(sys.argv[0], len(sys.argv), sys.argv)

And results:

PY$ ./test2.py 
i am test.py 
     and sys.argv[0] is: ./test2.py
     and len(sys.argv) is: 1 
     and sys.argv is: ['./test2.py']

PY$ ./test2.py 1 2 3 'str w/space'
i am test.py 
     and sys.argv[0] is: ./test2.py
     and len(sys.argv) is: 5 
     and sys.argv is: ['./test2.py', '1', '2', '3', 'str w/space']

PY$

Minutes left until midnight, take 2

Duh… while driving home today, I realized “why do I need to figure out tomorrow?” (To obtain “midnight”.)  Midnight is always the same number of minutes from the start of the day.  I.e. 24 hrs * 60 min/hour = 1,440 minutes in a day.

So, let’s get the minute that we’re at “now”… and then just subtract that from 1440 to get the minutes that are left in the day.   Thus take 2:

from datetime import datetime

now = datetime.now()
minutes_left = 24 * 60 - (now.hour * 60 + now.minute)

print "As of " + str(now) + \
      ", there are %d minutes until midnight" % minutes_left

Learnings & TODOs

Realized I do need to go back and learn more of the basics, to get a grasp on correct teminology.  Found this post which gave some nice pointers on things to review in the replies:

I give advise you to read thoroughly the parts

3.1. Objects , values and types http://docs.python.org/reference/datamodel.html#the-standard-type-hierarchy

and

4.1. Naming and binding http://docs.python.org/reference/executionmodel.html#naming-and-binding

References:

  • http://docs.python.org/2/library/datetime.html
  • http://stackoverflow.com/questions/5103329/how-to-find-out-what-methods-properties-etc-a-python-module-possesses

 

Minutes left until midnight

I often tend to do various things that involve date arithmetic, and thought, “hey, what’s a simple quicky problem to get my feet wet in exploring python’s date/time support?

How about, “how many minutes from now until midnight?”

Here’s the documentation for builtin module “datetime”.

A quick glance shows several types:  date, time, datetime and timedelta.  The timedelta looked interesting, and examples suggest I can do math with these types, too.

My thinking is do something like “tomorrow – now”, converted to minutes.

Messing around in IDLE, I got tripped up trying to perform timedelta operations with mixed types, and surprise, you can’t do that:

>>> import datetime
>>> date.today() + timedelta(1)
datetime.date(2013, 1, 6)
>>> datetime.now()
datetime.datetime(2013, 1, 5, 20, 12, 25, 131993)
>>> (date.today() + timedelta(1)) - datetime.now()

Traceback (most recent call last):
  File "<pyshell#285>", line 1, in 
    (date.today() + timedelta(1)) - datetime.now()
TypeError: unsupported operand type(s) for -: 'datetime.date' 
and 'datetime.datetime'
>>>

Ugh.

I gave up trying to use “date.today()”, and constructed ‘tomorrow’ as follows:

import datetime

def how_many_minutes_until_midnight():
     now = datetime.datetime.now()
     tomorrow = datetime.datetime(now.year, now.month, now.day) + \
                datetime.timedelta(1)
     return abs(tomorrow - now).seconds / 60

print how_many_minutes_until_midnight()

POSTSCRIPT

The code written above was the original solution I came up with. But, it’s a bit ugly I think.  Re-googling took me to these hits:

The latter whose comments revealed the use of time() and datetime.combine.  So the above example can be improved further, I think.  (Also note the different import statement.)

from datetime import *

def how_many_minutes_until_midnight():
    tomorrow = date.today() + timedelta(1)
    midnight = datetime.combine(tomorrow, time())
    now = datetime.now()
    return (midnight - now).seconds / 60

print how_many_minutes_until_midnight()

Learnings / TODOs

I did note the timedelta also has “total_seconds()” in addition to “seconds”, the latter which is only the seconds portion of the the overall delta which could span multiple days.  Since the nature of the calculation is within one day, either one is (mostly) equivalent for me:

    return (midnight - now).seconds / 60

and

    return (midnight - now).total_seconds() / 60

Testing these in IDLE showed that the latter returns a float, whereas the former is integer.  But both get the job done.

References