I have a method that accepts one or more optional arguments and I'd like to log them, following the best practice of lazy interpolation of log values:
def frobnicate(a: str, b: int, c: typing.Optional[float] = None):
# note: c = None actually means absence of data, it's not a placeholder for a default value
mylogger.debug('frobnicate(%s, %d, %.2f)', a, b, c)
Of course that code snipped doesn't work for c = None
because it chokes on the %f
.
My best solution so far is formatting directly as string:
def frobnicate(a: str, b: int, c: typing.Optional[float] = None):
mylogger.debug('frobnicate(%s, %d, %s)', a, b, c)
which semantically works and I use it most of the time, but has the problem of preventing control over the actual conversion of the not-None case. The latter could be done with something like this:
def frobnicate(a: str, b: int, c: typing.Optional[float] = None):
mylogger.debug(
'frobnicate(%s, %d, %s)', a, b,
f"{c:.2f}" if isinstance(c, float) else str(c)
)
This latter version is not only defeating lazy evaluation of logging arguments (because it is converting to string beforehand) but also making it worse with an additional isinstance()
check. I also find it a little ugly style-wise but that could be me :)
My current opinion here is that if we want to strictly adhere to lazy evaluation we have to stay within the limitations of %
-based string formatting and AFAIK there is not a way to express "use this format if not None
else tell me it's None". In the end it's either a choice between "accept whatever conversion format is the default" and "sacrifice lazy evaluation in exchange of control over string formatting".
I would like to know if my solution is aligned with general consensus or if anyone has a better take on this, maybe I'm missing some think-out-of-the-box stuff.
None
and renders that with the given values.