Why does python's `datetime.strptime` function not behave the same way when called using `functools.partial`?

Viewed 704

Here is an example of the error that I am facing:

In [1]: from functools import partial                                                                                             

In [2]: from datetime import datetime                                                                                             

In [3]: datetime.strptime("2/3/2016", "%m/%d/%Y")                                                                                 
Out[3]: datetime.datetime(2016, 2, 3, 0, 0)

In [4]: partial(datetime.strptime, "%m/%d/%Y")("2/3/2016")                                                                        
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-4-d803aff4879c> in <module>
----> 1 partial(datetime.strptime, "%m/%d/%Y")("2/3/2016")

~/miniconda3/envs/ROS/lib/python3.6/_strptime.py in _strptime_datetime(cls, data_string, format)
    563     """Return a class cls instance based on the input string and the
    564     format string."""
--> 565     tt, fraction = _strptime(data_string, format)
    566     tzname, gmtoff = tt[-2:]
    567     args = tt[:6] + (fraction,)

~/miniconda3/envs/ROS/lib/python3.6/_strptime.py in _strptime(data_string, format)
    360     if not found:
    361         raise ValueError("time data %r does not match format %r" %
--> 362                          (data_string, format))
    363     if len(data_string) != found.end():
    364         raise ValueError("unconverted data remains: %s" %

ValueError: time data '%m/%d/%Y' does not match format '2/3/2016'

How do I get datetime.strptime to behave properly using partial? Is this a problem with how I am using partial or how I am using datetime.strptime?

2 Answers

You're passing the format (which is supposed to be the second parameter) as first to strptime via partial, and then passing the date string (which is supposed to be the first parameter), leading to the error.

You can't use datetime.strptime with partial like that as it does not take any keyword argument. Instead you can use a regular function:

In [246]: def get_dt(string): 
     ...:     return datetime.strptime(string, "%m/%d/%Y") 
     ...:                                                                                                                                                                                                   

In [247]: get_dt("2/3/2016")                                                                                                                                                                                
Out[247]: datetime.datetime(2016, 2, 3, 0, 0)

Make sure to accept beemayl's answer. I just have a couple things to add that might be useful.

It's true the problem you saw was with the ordering of "arguments" to partial:

>>> from functools import partial  
>>> from datetime import datetime  
>>> datetime.strptime("2/3/2016", "%m/%d/%Y") 
datetime.datetime(2016, 2, 3, 0, 0)
>>> partial(datetime.strptime, "%m/%d/%Y")("2/3/2016")
Traceback (most recent call last):
    ValueError: time data '%m/%d/%Y' does not match format '2/3/2016'

And indeed if you reverse the arguments, partial works:

>>> partial(datetime.strptime, "2/3/2016")("%m/%d/%Y")
datetime.datetime(2016, 2, 3, 0, 0)

But this is not what you want, of course.

So, you might think you can take advantage of kwargs here...in fact, if you look at the docs, it says:

classmethod datetime.strptime(date_string, format)

Return a datetime corresponding to date_string, parsed according to format.

So let's try that:

>>> partial(datetime.strptime, format="%m/%d/%Y")("2/3/2016")
Traceback (most recent call last):
    TypeError: strptime() takes no keyword arguments

Takes no keyword arguments! WHAT?! Well yeah, most functions that "come from C" actually don't! By default, functions you write yourself in Python will always have kwargs, unless you use the cool new feature starting in Python 3.8 that lets you prohibit them.

Interestingly, if you make your own strptime:

>>> def my_strptime(date_string, format):
...     return datetime.strptime(date_string, format)
... 

Then you can do what you want!

>>> partial(my_strptime, format="%m/%d/%Y")("2/3/2016")
datetime.datetime(2016, 2, 3, 0, 0)

provided you use the kwarg format.

Related