18

I have a simple Python script that uses a signal handler for Ctl-C. If the program completes normally, the end time is passed into the "print_results" function. I wanted the print_results function to have an optional parameter that, if not passed, simply gets the current "now" time. But when I call it from the signal handler, it does not get the correct time.

Here is my simplified, but reproducible, program:

import sys
import signal
import urllib2
import urllib
import datetime
import time
import getopt,sys

def signal_handler(signal, frame):
    print_results()
    sys.exit(0)

def print_results(ended=datetime.datetime.now()):
    print "\nEnded at ",ended
    print "Total time: ",(ended - startTime)
    print "Finished ",numIterations," iterations, received ",totalRecords," records"

    numIterations = 0
    maxIterations = 8
    delaySecs = 3
    totalRecords = 0

    # set up signal handler
    signal.signal(signal.SIGINT, signal_handler)

    startTime = datetime.datetime.now()

    print "Starting at ",time.asctime(time.localtime())

    while (numIterations < maxIterations):

        iterStartTime = datetime.datetime.now()

        numIterations += 1

        print "Iteration: ",numIterations

        # sleep if necessary

        if delaySecs > 0:
            time.sleep(delaySecs)

        iterEndTime = datetime.datetime.now()

        print "Iteration time: ",(iterEndTime - iterStartTime)

    endTime = datetime.datetime.now()

    print "Ended at ",time.asctime(time.localtime())
    print "Total test time: ",(endTime - startTime)

    print_results(endTime)

Here is what happens when I type Ctl-C

$ python test.py                                                           
Starting at  Fri Jun 15 08:28:15 2012
Iteration:  1
Iteration time:  0:00:03.003101
Iteration:  2
Iteration time:  0:00:03.003105
Iteration:  3
^C
Ended at  2012-06-15 08:28:15.766496
Total time:  -1 day, 23:59:59.999964
Finished  3  iterations, received  0  records

It seems like that when print_results is called with no arguments that the 'ended' value is not being interpreted correctly as a datetime object. But since Python does not have a way to cast (as far as I can tell), I cannot tell what is wrong.

Thanks in advance,

Mitch

Joel Cornett
  • 24,192
  • 9
  • 66
  • 88
mitchmcc
  • 369
  • 3
  • 5
  • 18

1 Answers1

63

The problem you are having is that you are evaluating the function in the parameter. This means that ended=datetime.datetime.now() takes the value of the time when this is being parsed, not when it is called. What you should do is something like this:

def print_results(ended=None):
    if ended is None:
        ended = datetime.datetime.now()
    ...

Here there is a really good explanation of why this happens: “Least Astonishment” in Python: The Mutable Default Argument

Community
  • 1
  • 1
Santiago Alessandri
  • 6,630
  • 30
  • 46
  • 4
    [This comes up a lot](http://stackoverflow.com/questions/1132941/least-astonishment-in-python-the-mutable-default-argument). – JoeFish Jun 15 '12 at 12:45
  • 1
    Thanks... that makes sense, but I clearly expected it to be evaluated at run-time... – mitchmcc Jun 15 '12 at 12:54
  • @mitchmcc I think we all did when we started learning Python :) – JoeFish Jun 15 '12 at 12:55
  • I introduced a bug this way by naively using datetime.date.today() as a default argument. Curiously calling it repeatedly returns a variety of different days - not just one. – Alexander Nov 01 '18 at 18:38
  • how about passing the object like `def print_results(ended=datetime.datetime.now):` – disooqi Mar 05 '21 at 12:38
  • However, correct me if I'm wrong, having a default argument like `def print_results(ended=datetime.timedelta(minutes=10))` is totally fine. It's immutable and always evaluates to the same thing. – Dr_Zaszuś Feb 09 '22 at 16:30