Issue 23479: str.format() breaks object duck typing (original) (raw)

While porting some old code, I found some interesting misbehavior in the new-style string formatting. When formatting objects which support int and float conversion, old-style percent formatting works great, but new-style formatting explodes hard.

Here's a basic example:

class MyType(object): def init(self, func): self.func = func

def __float__(self):
    return float(self.func())

print '%f' % MyType(lambda: 3)

Output (python2 and python3): 3.000000

print '{:f}'.format(MyType(lambda: 3))

Output (python2):

Traceback (most recent call last):

File "tmp.py", line 28, in

print '{:f}'.format(MyType(lambda: 3))

ValueError: Unknown format code 'f' for object of type 'str'

Output (python3.4):

Traceback (most recent call last):

File "tmp.py", line 30, in

print('{:f}'.format(MyType(lambda: 3)))

TypeError: non-empty format string passed to object.format

And the same holds true for int and so forth. I would expect these behaviors to be the same between the two formatting styles, and tangentially, expect a more python2-like error message for the python 3 case.

As David says, the change from: ValueError: Unknown format code 'f' for object of type 'str' to: TypeError: non-empty format string passed to object.format is quite intentional.

Let me address the differences between %-formatting and format-based formatting. In these examples, let's say you're trying to format an object o=MyType(whatever).

With your '%f' example, you're saying "please convert o to a float, and then format and print the result". The %-formatting code knows a priori that the type must be converted to a float.

With your {:f} example, you're saying "please call o.format('f'), and print the result". Nowhere is there any logic that says "well, f must mean that o must be converted to a float". The decision on conversion (if any) is left to MyType.format, as are all other formatting decisions. You could write something like:

class MyType(object): def format(self, fmt): if fmt.endswith('f'): return float(self.func()).format(fmt) elif fmt.endswith('d'): return int(self.func()).format(fmt) else: return str(self.func()).format(fmt)

def __init__(self, func):
    self.func = func

print(format(MyType(lambda: 3), '.12f')) # produces "3.000000000000" print(format(MyType(lambda: 3), '05d')) # produces "00003" print(format(MyType(lambda: 3), '^10s')) # produces "3"

Note that %-formatting only supports a fixed and limited number of types: basically int, float, and str. It cannot support new type of objects with their own format strings.

With format-formatting, every type can specify how it wants to be formatted, and can specify its own format language. For example, datetime supports a rich formatting language (based on strftime).