HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Advanced formatting with argument passing

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
formattingargumentwithpassingadvanced

Problem

I want to customise how my objects are printed using the .format() function.

I have two problems to make that happen in clear and concise way. But since the second one stems from the first one I'm presenting both here.

Formatting array

Passing options to .format() in array seems to me to be awfully messy if I want to match default behaviour with __repr__:

import binascii

def format_data(data, formatstr):
    if 'x' in formatstr:
        return binascii.b2a_hex(data).decode('ascii')
    else:
        return repr(data)

class InsideA(object):
    def __repr__(self):
        return "InsideA()"

    def __format__(self, _):
        return "InsideA()"

class InsideB(object):
    def __init__(self, data):
        self.data = data

    def __repr__(self):
        data = format_data(self.data, 'x')
        return "InsideB(data={0})".format(data)

    def __format__(self, formatstr):
        data = format_data(self.data, formatstr)
        return "InsideB(data={0})".format(data)

if __name__ == "__main__":
    a = InsideA()
    b = InsideB(bytearray(b'test\xff\xff'))

    array = [a, b]

    print("what I want:")
    print(array)
    print("")

    print("doesn't work:")
    try:
        print("{0:x}".format(array))
    except Exception as e:  # catch just to continue execution
        print(e)
    print("")

    print("Leaves ' around elements:")
    print(["{0:x}".format(elem) for elem in array])
    print("")

    print("Works but seems overly complex:")
    print("[" + ", ".join("{0:x}".format(elem) for elem in array) + "]")


Produces:

What I want:

[InsideA(), InsideB(data=74657374ffff)]


Doesn't work:

non-empty format string passed to object.__format__


Leaves ' around elements:

['InsideA()', 'InsideB(data=74657374ffff)']


Works but seems overly complex:

[InsideA(), InsideB(data=74657374ffff)]


Formatting with non-uniform objects

The problem only gets compounded if the array elements don't always implement the format function, requ

Solution

list always uses repr on it's elements.
When you str or repr it.
And internally list is doing roughly the equivalent of what you are doing in format_array.

The only way to make this work is to use something like format_array,
or subclass list.
But they will both be doing the same thing, just depends on how much sugar you want.

Your code is nice, readable and follows PEP8.
As this is tending towards example code, it's not in it's native environment, it's hard to comment distinctively on it.

But format_data can be merged with the class.

class InsideB(object):
    def __init__(self, data):
        self.data = data

    def __repr__(self):
        data = self._data(True)
        return "InsideB(data={0})".format(data)

    def __format__(self, formatstr):
        data = self._data('x' in formatstr)
        return "InsideB(data={0})".format(data)

    def _data(self, as_hex):
        if as_hex:
            return binascii.b2a_hex(self.data).decode('ascii')
        else:
            return repr(self.data)


You can then add an option to use 'hex' or repr as the output type to the class.
With the option to reverting to the original after a use.
Obviously it depends on your usage.

class InsideB(object):
    def __init__(self, data, as_hex=False, revert_hex=False):
        self.data = data
        self._as_hex = as_hex
        self.output_type = as_hex
        self._revert_hex = revert_hex

    def _data(self, as_hex):
        if as_hex or self.output_type:
            data = binascii.b2a_hex(self.data).decode('ascii')
        else:
            data = repr(self.data)

        if self._revert_hex:
            self.output_type = self._as_hex

        return data


Then you would need to make a function that maps over all the items in the list,
changes output_type to what you want, and you'll be in the same position as you are now.
But with a bit more sugar.

If you choose to subclass list, and to use str in __str__,
then you'll get output which is just wrong.

>>> print('{!s}, {!s}'.format('a', 'b'))
a, b
>>> print('{!r}, {!r}'.format('a', 'b'))
'a', 'b'


No matter how I look at it you wont be able to change the output 'nicely'.
Unless you think something more robust to map(lambda i: i.output_type = True, array) is nice.

Bottom line you have to manually implement this, as repr is hard-coded.

Code Snippets

class InsideB(object):
    def __init__(self, data):
        self.data = data

    def __repr__(self):
        data = self._data(True)
        return "InsideB(data={0})".format(data)

    def __format__(self, formatstr):
        data = self._data('x' in formatstr)
        return "InsideB(data={0})".format(data)

    def _data(self, as_hex):
        if as_hex:
            return binascii.b2a_hex(self.data).decode('ascii')
        else:
            return repr(self.data)
class InsideB(object):
    def __init__(self, data, as_hex=False, revert_hex=False):
        self.data = data
        self._as_hex = as_hex
        self.output_type = as_hex
        self._revert_hex = revert_hex

    def _data(self, as_hex):
        if as_hex or self.output_type:
            data = binascii.b2a_hex(self.data).decode('ascii')
        else:
            data = repr(self.data)

        if self._revert_hex:
            self.output_type = self._as_hex

        return data
>>> print('{!s}, {!s}'.format('a', 'b'))
a, b
>>> print('{!r}, {!r}'.format('a', 'b'))
'a', 'b'

Context

StackExchange Code Review Q#113825, answer score: 3

Revisions (0)

No revisions yet.