4

If there is a variable a whose type is unknown until it is created, how to account for the type when using python string formatting, specifically at the time of formatting?

Example:

import numpy as np
import datetime as dt

a = dt.datetime(2020,1,1)
print(f'{a:%d/%m/%Y}')

>>>
01/01/2020

However if the variable is changed, then this will produce an error:

a = np.nan
print(f'{a:%d/%m/%Y}')

>>>
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-70-02594a38949f> in <module>
      1 a = np.nan
----> 2 print(f'{a:%d/%m/%Y}')

ValueError: Invalid format specifier

So I am attempting to implement something like:

print(f'{a:%d/%m/%Y if not np.isnan(a) else ,.0f}')

>>>
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-78-52c24b6abb16> in <module>
----> 1 print(f'{a:%d/%m/%Y if not np.isnan(a) else ,.0f}')

ValueError: Invalid format specifier

However, as we can see it attempts to format the variable before evaluating the expression. Perhaps the syntax needs correcting or another approach is required entirely. However, I do specifically want to evaluate which formatter to deploy it and deploy it at the time of printing the f-string.

Thanks!


Realise that the following approach is possible:

print(f'{a:%d/%m/%Y}' if isinstance(a,dt.datetime) else f'{a}')

However, the syntax is still too cumbersome, especially if this is to be deployed in mutliple locations. Effectively am searching for a streamlined way to format the variable if it is of a compatible type to the format and if not, then default to no specified formatter.

Thanks!

agftrading
  • 784
  • 3
  • 8
  • 21
  • With f-strings this appears to be difficult. I think you could look into making a custom string formatter. See https://docs.python.org/3.10/library/string.html#custom-string-formatting. See also https://stackoverflow.com/questions/57570026/how-to-provide-custom-formatting-from-format-string – K.Cl Jul 02 '22 at 19:53

3 Answers3

3

What about a custom function, so that you can handle all the types you need in a single point in your code?

import numbers

def get_formatter(var):
    """Get the format of a variable to be used in an f-string, based on its type"""
    if isinstance(var, (dt.datetime, dt.date)):
        return '%d/%m/%Y'
    elif isinstance(var, numbers.Number):
        if np.isfinite(var):
            return '.3f'

    return ''

You can then you the function to format the variable; for instance:

for v in [
    dt.datetime(2020,1,1),
    np.nan,
    5,
    5.555555,
]:
    print(f'{v:{get_formatter(v)}}')

will produce:

01/01/2020
nan
5.000
5.556
PieCot
  • 3,564
  • 1
  • 12
  • 20
1

I'd recommend to define your own custom formatter if you have many such kind of things to do.

For example:

import string
import datetime as dt

class CustomFormatter(string.Formatter):
    def format_field(self, value, format_spec):
        if isinstance(value, float):
            return str(value)
        elif isinstance(value,dt.datetime):
            return value.__format__(format_spec)
        return super().format(value, format_spec)

In this custom formatter, you can ignore all the format specifiers for floats and call the __format__() method for datetimes or change them to anything else you want. And while using, you just:

print(fmt.format("{:%d/%m/%Y}",a))

This is useful when you'd like use like this:

a = dt.datetime(2020,1,1)
b = np.nan
fmt = CustomFormatter()
for value in [a,b]:
    print(fmt.format("The value is: {:%d/%m/%Y}",value))

The output will be:

The value is: 01/01/2020
The value is: nan

which means you do not need to change your specifier for datetime or float or any possible types and not even checking the types, cause you've done this and put it in your class.

C.K.
  • 1,409
  • 10
  • 20
0

You could make an inline conditional format like so:

print(f'{a:{"%d/%m/%Y" if not np.isnan(a) else ",.0f"}}')

So basically you needed an extra pair of curly brackets, however, I think the solution proposed by @PieCot is much cleaner.

Max
  • 71
  • 5