In general I think your code is pretty good. It is a simple function, and it does not need to get fancy.
I do think one bit could be a bit cleaner. I believe this format string:
format_str = lambda v1, v2, p: f"{v1:0>2}{p[0]}{v2:0>2}{p[1]}"
format_str(d, h, ("d","h"))
would be simpler and therefore clearer as:
format_str = "{:0>2}{}{:0>2}{}"
format_str.format(d, 'd', h, 'h')
I think the lambda
here is not helping readability.
More Pythonic?
For this simple case I don't think you really need anything more Pythonic. But let's have some fun and illustrate using some Python libraries.
datetime
library
First thing to consider is maybe using standard Python lib for doing some of the date and time math. The library could build those values for you like so:
import datetime as dt
# build a datetime from the number of seconds passed in
a_time = dt.datetime.utcfromtimestamp(seconds)
# get d, h, m, s
d, h, m, s = dt.timedelta(seconds=seconds).days, a_time.hour, a_time.minute, a_time.second
Stacked ifs
You asked specifically about the stacked ifs. In this case the ifs are not stacked very deep, so what follows is rather severe overkill :-)
Here is a general way to get the elements following the first non-zero element.
# get values fllowing first non zero value and their labels
non_zero = tuple(it.dropwhile(lambda x: not x[0], zip(values, 'dhms')))
Yikes, this is that an eyesore! But is does show how to get the elements following the first non-zero element in a general way, IE: No stacked ifs needed.
How does that work?
Let's break it down a bit. Starting from the inside:
zip(values, 'dhms')
This will produce a sequence of tuples looking something like:
[(0, 'd'), (1, 'h'), (1, 'm'), (5, 's')]
Note how the use of zip()
allows the labels to be associated with their elements. This allows the next step to keep the labels with their values.
Then:
it.dropwhile(lambda x: not x[0], zip(values, 'dhms')))
uses itertools.dropwhile()
to drop the leading tuples which start with 0
. Finally:
non_zero = tuple(it.dropwhile(lambda x: not x[0], zip(values, 'dhms')))
will produce a tuple so that we can get the number of elements to help produce the format string:
# a format string
format_str = "{:0>2}{}{:0>2}{}" if len(non_zero) > 1 else " {:0>2}sec"
And then bring it all together with:
# format the string
return format_str.format(*it.chain.from_iterable(non_zero or ((0, 0),)))
itertools.chain.from_iterable()
flattens the non_zero
tuple, to make the format()
a little easier:
Complete recast function
import datetime as dt
import itertools as it
def pprint_time2(seconds):
# build a datetime from the number of seconds passed in
a_time = dt.datetime.utcfromtimestamp(seconds)
# get d, h, m, s
values = dt.timedelta(seconds=seconds).days, a_time.hour, a_time.minute, a_time.second
# get values following first non zero value and their labels
non_zero = tuple(it.dropwhile(lambda x: not x[0], zip(values, 'dhms')))
# a format string
format_str = "{:0>2}{}{:0>2}{}" if len(non_zero) > 1 else " {:0>2}sec"
# format the string
return format_str.format(*it.chain.from_iterable(non_zero or ((0, 0),)))