Diego F. Rodr&#237;guez V. - 5 months ago 46
Python Question

# Can't use partial as __str__

I ran into this problem while trying to write a pretty print procedure for a program in which I use several named tuples containing floating point pairs.

``````from collections import namedtuple
Position = namedtuple('Position', 'x y')
Vector = namedtuple('Vector', 'x y')
Size = namedtuple('Size', 'width height')
``````

I want to format the floating point numbers when printed because the result of:

``````import math
print(Position(math.pi, math.pi), Vector(math.pi, math.pi), Size(math.pi, math.pi))
``````

Is too long:

``````Position(x=3.141592653589793, y=3.141592653589793) Vector(x=3.141592653589793, y=3.141592653589793) Size(width=3.141592653589793, height=3.141592653589793)
``````

So I created a function to print the named tuples:

``````def pretty_float_pair(name, labels, obj):
"""
If labels = ('a', 'b') and object = (1.2345, 1.2345) returns:
'name(a=1.23, b=1.23)'
"""
return '{}({}={:.2f}, {}={:.2f})'.format(name, labels[0], obj[0], labels[1], obj[1])
``````

The name and labels should be fixed for every type and only the obj argument varies so I thought I could use functools partial.

``````from functools import partial
Position.__str__ = partial(pretty_float_pair, 'Position', ('x', 'y'))
Vector.__str__ = partial(pretty_float_pair, 'Vector', ('x', 'y'))
Size.__str__ = partial(pretty_float_pair, 'Size', ('width', 'height'))
print(Position(math.pi, math.pi), Vector(math.pi, math.pi), Size(math.pi, math.pi))
``````

But this throws a
`TypeError: pretty_float_pair() missing 1 required positional argument: 'obj'.`

Surprisingly if I use lambda to create the functions it works.

``````Position.__str__ = lambda x: pretty_float_pair('Position', ('x', 'y'), x)
Vector.__str__ = lambda x: pretty_float_pair('Vector', ('x', 'y'), x)
Size.__str__ = lambda x: pretty_float_pair('Size', ('width', 'height'), x)
print(Position(math.pi, math.pi), Vector(math.pi, math.pi), Size(math.pi, math.pi))
``````

Prints what I wanted:

``````Position(x=3.14, y=3.14) Vector(x=3.14, y=3.14) Size(width=3.14, height=3.14)
``````

I'm trying to understand why the partial version doesn't work.

`functools.partial` returns a non-descriptor callable, roughly equivalent to an unbound method. This means that it is not being passed a `self` parameter, which is consistent with the error you are seeing.

Since a lambda behaves just like a regular function defined with `def`, it is in fact a descriptor. The `__get__` method of the lambda returns a bound version that passes in the instance as `x`.

To get a partial function that behaves more like a method, use `functools.partialmethod` instead. You will have to move `obj` to the beginning of your argument list so it can receive `self` when the method is bound.

```from functools import partialmethod

def pretty_float_pair(obj, name, labels):
"""
If labels = ('a', 'b') and object = (1.2345, 1.2345), returns:
name(a=1.23, b=1.23)
"""
return '{}({}={:.2f}, {}={:.2f})'.format(name, labels[0], obj[0], labels[1], obj[1])

Position.__str__ = partialmethod(pretty_float_pair, 'Position', ('x', 'y'))
Vector.__str__ = partialmethod(pretty_float_pair, 'Vector', ('x', 'y'))
Size.__str__ = partialmethod(pretty_float_pair, 'Size', ('width', 'height'))

print(Position(math.pi, math.pi), Vector(math.pi, math.pi), Size(math.pi, math.pi))
```
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download