What does colon mean in Python string formatting? - python

I am learning the Python string format() method. Though I understand that {} is a placeholder for arguments, I am not sure what : represent in the following code snippet from Programiz tutorial:
import datetime
# datetime formatting
date = datetime.datetime.now()
print("It's now: {:%Y/%m/%d %H:%M:%S}".format(date))
# custom __format__() method
class Person:
def __format__(self, format):
if(format == 'age'):
return '23'
return 'None'
print("Adam's age is: {:age}".format(Person()))
Why is there a : in front of %Y in print("It's now: {:%Y/%m/%d...? The code outputs It's now: 2021, and there is no : in front of 2021.
Why is there a : in front of age in print("Adam's age is: {:age}...?
Thanks in advance for your valuable input!!

Everything after : is a parameter to the __format__() method of the class of the corresponding arguent. For instance, for a number you can write {:.2f} to format it as a decimal number with 2 digits of precision after the decimal point.
For a datetime value, it's a format string that could be used with datetime.strftime().
And in your Person class, it will be passed as the format argument to Person.__format__(). So if you don't put :age there, the if condition will fail and it will print None instead of 23.

Python objects decide for themselves how they should be formatted using the __format__ method. Mostly we just use the defaults that come with the basic types, but much like __str__ and __repr__ we can customize. The stuff after the colon : is the parameter to __format__.
>>> class Foo:
... def __format__(self, spec):
... print(repr(spec))
... return "I will stubbornly refuse your format"
...
>>> f = Foo()
>>> print("Its now {:myformat}".format(f))
'myformat'
Its now I will stubbornly refuse your format
we can call the formatter ourselves. datetime uses the strftime format rules.
>>> import datetime
>>> # datetime formatting
>>> date = datetime.datetime.now()
>>> print("It's now: {:%Y/%m/%d %H:%M:%S}".format(date))
It's now: 2021/10/04 11:12:23
>>> date.__format__(":%Y/%m/%d %H:%M:%S")
':2021/10/04 11:12:23'
Your custom Person class implemented __format__ and used the format specifier after the colon to return a value.

Try f-strings. In them the colon seems to be more reasonable. It delimits the variable name and its formatting options:
import datetime
# datetime formatting
date = datetime.datetime.now()
print(f"It's now: {date:%Y/%m/%d %H:%M:%S}")
# custom __format__() method
class Person:
def __format__(self, format):
if(format == 'age'):
return '23'
return 'None'
print(f"Adam's age is: {Person():age}")
Btw you can have similar functionality with keyword arguments to format():
print("It's now: {d:%Y/%m/%d %H:%M:%S}".format(d=date))
print("Adam's age is: {adam:age}".format(adam=Person()))

Related

Formatting problem from string to Datetime [duplicate]

This question already has answers here:
What is the difference between __str__ and __repr__?
(28 answers)
Closed 5 months ago.
I'm trying to parse a string with the datetime standard library module and then display it.
When I try this code, the output looks correct:
>>> import datetime
>>> endDate = datetime.datetime.strptime("2022-05-03", '%Y-%m-%d').date()
>>> print(endDate)
2022-05-03
But it changes if the endDate is put into a list or tuple:
>>> print([endDate])
[datetime.date(2022, 5, 3)]
Why does the output have the "datetime.date" text?
>>> from datetime import datetime
>>> endDate = datetime.strptime("2022-05-03", '%Y-%m-%d').date()
When you printed the date object, it used str representation of the date object.
>>> str(endDate)
'2022-05-03'
When you included that in a container and printed it, the container internally uses repr representation of the object
>>> repr(endDate)
'datetime.date(2022, 5, 3)'
print function by default converts all the objects passed to it to a String with str.
All non-keyword arguments are converted to strings like str() does
To understand this better, we can create a class which implements both __str__ and __repr__ functions like this
>>> class Test:
... def __str__(self):
... return "TEST_FROM_STR"
...
... def __repr__(self):
... return "TEST_FROM_REPR"
...
Now, we can create instances of that class and print them separately and with list, like this
>>> Test()
TEST_FROM_REPR
>>> [Test()]
[TEST_FROM_REPR]
>>> print(Test())
TEST_FROM_STR
>>> print([Test()])
[TEST_FROM_REPR]

Creating new conversion specifier in Python

In python we have conversion specifier like
'{0!s}'.format(10)
which prints
'10'
How can I make my own conversion specifiers like
'{0!d}'.format(4561321)
which print integers in following format
4,561,321
Or converts it into binary like
'{0!b}'.format(2)
which prints
10
What are the classes I need to inherit and which functions I need to modify? If possible please provide a small example.
Thanks!!
What you want to do is impossible, because built-in types cannot be modified and literals always refer to built-in types.
There is a special method to handle the formatting of values, that is __format__, however it only handles the format string, not the conversion specifier, i.e. you can customize how {0:d} is handled but not how {0!d} is. The only things that work with ! are s and r.
Note that d and b already exist as format specifiers:
>>> '{0:b}'.format(2)
'10'
In any case you could implement your own class that handles formatting:
class MyInt:
def __init__(self, value):
self.value = value
def __format__(self, fmt):
if fmt == 'd':
text = list(str(self.value))
elif fmt == 'b':
text = list(bin(self.value)[2:])
for i in range(len(text)-3, 0, -3):
text.insert(i, ',')
return ''.join(text)
Used as:
>>> '{0:d}'.format(MyInt(5000000))
5,000,000
>>> '{0:b}'.format(MyInt(8))
1,000
Try not to make your own and try to use default functions already present in python. You can use,
'{0:b}'.format(2) # for binary
'{0:d}'.format(2) # for integer
'{0:x}'.format(2) # for hexadecimal
'{0:f}'.format(2) # for float
'{0:e}'.format(2) # for exponential
Please refer https://docs.python.org/2/library/string.html#formatspec for more.

How to let datetime.strptime parse zero-padded decimal number only [duplicate]

I've been using the datetime module to do some checking of dates to see if they are in mm/dd/yyyy or mm/dd/yy formats. The problem is that the %d and %m directives aren't sensitive enough to detect when the month or day is a single digit, which is a requirement of mine.
datetime.strptime('01/01/2001', '%m/%d/%Y')
works like I want it to, but
datetime.strptime('1/1/2001', '%m/%d/%Y')
also produces a valid datetime, when I really want it to throw a ValueError unless the month and day are 0-padded. Does anyone know how to set a required precision for datetime formats? Is this possible or should I just go with regex instead?
The datetime function you're using isn't intended to validate input, only to convert strings to datetime objects. Both of your examples are legitimate string inputs as far as datetime is concerned.
If you want to enforce user input to be a specific format, I would go with a regex - see this example from my REPL:
>>> import re
>>> pattern = re.compile(r"^[0-9]{2}/[0-9]{2}/[0-9]{4}$")
>>> def valid_datestring(datestring):
... if pattern.match(datestring):
... return True
... return False
...
>>> valid_datestring('1/1/2001')
False
>>> valid_datestring('01/01/2001')
True
If you want to define a function that returns a formatted date or returns a valueError, you can do something like this:
def format_datestring(datestring):
if not valid_datestring(datestring):
raise ValueError('Date input must be in the form dd/mm/yyyy!')
return datetime.strptime(datestring, '%m/%d/%Y')
You can verify that a string conforms exactly to a formatting string by re-formatting the datetime and comparing to the original string:
datetime_ = datetime.strptime(datetime_string, DATETIME_FORMAT)
if datetime_.strftime(DATETIME_FORMAT) != datetime_string:
[fail]

Python string format: When to use !s conversion flag

What's the difference between these 2 string format statements in Python:
'{0}'.format(a)
'{0!s}'.format(a)
Both have the same output if a is an integer, list or dictionary. Is the first one {0} doing an implicit str() call?
Source
PS: keywords: exclamation / bang "!s" formatting
It is mentioned in the documentation:
The conversion field causes a type coercion before formatting.
Normally, the job of formatting a value is done by the __format__()
method of the value itself. However, in some cases it is desirable to
force a type to be formatted as a string, overriding its own
definition of formatting. By converting the value to a string before
calling __format__(), the normal formatting logic is bypassed.
Two conversion flags are currently supported: '!s' which calls
str() on the value, and '!r' which calls repr().
An example can be taken (again from the documentation) to show the difference:
>>> "repr() shows quotes: {!r}; str() doesn't: {!s}".format('test1', 'test2')
"repr() shows quotes: 'test1'; str() doesn't: test2"
Simply said:
'{0}'.format(a) will use the result of a.__format__() to display the value
'{0!s}'.format(a) will use the result of a.__str__() to display the value
'{0!r}'.format(a) will use the result of a.__repr__() to display the value
>>> class C:
... def __str__(self): return "str"
... def __repr__(self): return "repr"
... def __format__(self, format_spec): return "format as " + str(type(format_spec))
...
>>> c = C()
>>> print "{0}".format(c)
format as <type 'str'>
>>> print u"{0}".format(c)
format as <type 'unicode'>
>>> print "{0!s}".format(c)
str
>>> print "{0!r}".format(c)
repr
Concerning the second argument of __format__, to quote PEP 3101 "Controlling Formatting on a Per-Type Basis":
The 'format_spec' argument will be either
a string object or a unicode object, depending on the type of the
original format string. The __format__ method should test the type
of the specifiers parameter to determine whether to return a string or
unicode object. It is the responsibility of the __format__ method
to return an object of the proper type.
Thanks to the comment & answer from #hjpotter92 for explanation:
Here's an example that shows the difference (it's when you override the __format__ method)
class MyClass:
i = 12345
def __format__(self, i):
return 'I Override'
>>> obj = MyClass()
>>> '{0}'.format(obj)
'I Override'
>>> '{0!s}'.format(obj)
'<__main__.MyClass instance at 0x021AA6C0>'

String formatting with “{0:d}” doesn't convert to integer

This is about the same issue as in this question about floats.
When you've got a value that could get converted to an integer, the old %d would convert it, but format doesn't.
class MyIntegerTenClass:
def __int__(self):
return 10
def __str__(self):
return 'ten'
ten = MyIntegerTenClass()
print '%d, %02X, %s' % (ten, ten, ten) # ok
print '{0}'.format(ten) # ok
print '{0:d}, {0:02X}'.format(ten) # ValueError: Unknown format code 'd' for object of type 'str'
Is there a way to modify the behaviour of format, without touching the class of the value to be formatted (without adding a __format__ method to that class)?
Edit: My goal is to get the formatting dependent on the format string, but not on the value.
So if the format string says "d" or "x", convert the value to int and then to decimal or hexadecimal representation.
If the format string says "s", convert it to string directly. As the old % did.
Actually, I could even add a __format__ method to the class of the value. But how do I check, in that method, if the given format specification is an integer format specification? Without reimplementing the format specification parser of the builtin format.
Edit: Here's a solution with __format__ and exceptions. Any better ideas?
class MyIntegerTenClass:
def __int__(self):
return 10
def __str__(self):
return 'ten'
def __format__(self, spec):
fmt = '{0:%s}'%spec
try:
return fmt.format(str(self))
except:
return fmt.format(int(self))
ten = MyIntegerTenClass()
print '%d, %02X, %s' % (ten, ten, ten) # ok, prints "10, 0A, ten"
print '{0:d}, {0:02X}, {0}'.format(ten) # ok, prints "10, 0A, ten"
A first approach to this problem might be simply to try it:
class MyIntegerTenClass:
def __int__(self):
return 10
def __str__(self):
return 'ten'
def __format__(self, format_spec):
try:
s = format(str(self), format_spec)
except ValueError:
s = format(int(self), format_spec)
return s
If MyIntegerTenClass can be inserted into the format string as a string, it will be. If not, it will be converted into an int and resubmitted to format.
>>> print '{0}, {0:s}, {0:d}, {0:02X}, {0:f}'.format(ten)
ten, ten, 10, 0A, 10.000000
If you want the default representation to be 10 instead of ten, you need only swap the conversion lines:
def __format__(self, format_spec):
try:
s = format(int(self), format_spec)
except ValueError:
s = format(str(self), format_spec)
return s
Test output:
>>> print '{0}, {0:s}, {0:d}, {0:02X}, {0:f}'.format(ten)
10, ten, 10, 0A, 10.000000
As an addendum, I don't believe you'll be able to get the behavior you want without defining __format__; however, you can develop a more sophisticated approach using a Formatter object. Still, I think the exception-based approach gives you a lot of built-in functionality for free.
pass an integer instead of a string and it'll work fine.
>>> print '{0:d}'.format(1)
1
>>> print '{0:d}'.format('1')
Traceback (most recent call last):
File "<pyshell#57>", line 1, in <module>
print '{0:d}'.format('1')
ValueError: Unknown format code 'd' for object of type 'str'
It says ten is a string, you are trying to format it as a number.
Try wrapping it in a cast:
>>> print '{0:d}, {0:02X}'.format(int(ten))
10, 0A

Categories

Resources