Python pretty print nested objects
Asked Answered
A

3

5

I have 3 python classes A, B and C. A contains B object and B contains C's object. What I want is when I print A class object, it should pretty print in the below format. There can be more nesting also inside C class.

A:
    loc : XYZ
    qual : ABC
    b :
        name : ABC
        age : 30
        c :
            address : ABC
            phn : 99009

Below are the classes for reference.

class C(object):
    def __init__(self):
        self.address='ABC'
        self.phn=99009

class B(object):
    def __init__(self):
        self.name='ABC'
        self.age=30
        self.c = C()

class A(object):
    def __init__(self):
        self.loc = 'XYZ'
        self.qual = 'ABC'
        self.b = B()
Akilahakili answered 8/8, 2018 at 19:3 Comment(1)
Since you mention pretty printing, and use the tag pretty-print, I'm assuming you're just looking to control how the pprint module handles your objects? The simplest thing to do is to create a custom PrettyPrinter subclass that overrides the methods documented there, so it handles your objects as "recursive objects" like lists and dicts. Then you just use an instance of that class in place of the pprint module.Byproduct
F
9

The following works by letting your classes inherit from a common base class implementing the __str__ method:

class PrettyPrinter(object):
    def __str__(self):
        lines = [self.__class__.__name__ + ':']
        for key, val in vars(self).items():
            lines += '{}: {}'.format(key, val).split('\n')
        return '\n    '.join(lines)

class C(PrettyPrinter):
    def __init__(self):
        self.address='ABC'
        self.phn=99009

class B(PrettyPrinter):
    def __init__(self):
        self.name='ABC'
        self.age=30
        self.c = C()

class A(PrettyPrinter):
    def __init__(self):
        self.loc = 'XYZ'
        self.qual = 'ABC'
        self.b = B()

a = A()
print(a)

In Python 3.6 and newer, this displays like

A:
    loc: XYZ
    qual: ABC
    b: B:
        name: ABC
        age: 30
        c: C:
            address: ABC
            phn: 99009

Note that all attributes are automatically printed. Their print order is determined by the vars function, which really looks in the __dict__ dictionary. This dictionary has a nondeterministic order in Python 3.5 and below, and so the printout is not nearly as nice as in 3.6 and up.

Finitude answered 8/8, 2018 at 19:50 Comment(0)
D
6

The following recursive funciton works from scratch using the __dict__ attr. of the classes to get key:value pairs of the class' attributes. From here, we just test if the value is another class (in which case we call ourselves again), otherwise we simply print it in your desired format.

The only other thing to keep track of is the current indent of what level this line is currently printing at. This can be easily done with a default parameter (indent) that we increment before each recursion.

def pretty_print(clas, indent=0):
    print(' ' * indent +  type(clas).__name__ +  ':')
    indent += 4
    for k,v in clas.__dict__.items():
        if '__dict__' in dir(v):
            pretty_print(v,indent)
        else:
            print(' ' * indent +  k + ': ' + str(v))

and it works:

>>> pretty_print(A())
A:
    loc: XYZ
    qual: ABC
    B:
        name: ABC
        age: 30
        C:
            address: ABC
            phn: 99009
Devonne answered 8/8, 2018 at 19:40 Comment(4)
Just what I needed, tyColombes
I'm not the question askerColombes
@Colombes oh okDevonne
This PrettyPrinter stuff is all well and good, but most of the time all you need to do is just read the damn output. thank you for saving a headache. +1Hanghangar
C
-1
import re

def str_pretty(obj, indent=1, rec=0, key=''):
    """Returns: pretty str of an object
    obj: the object to print
    indent: the indent per depth
    rec: used in recursion
    key: variable name to print 
    """
    # Init
    s_indent = ' ' * indent * rec
    items = {}
    stg = s_indent

    if key != '': stg += str(key) + ': '

    # Discriminate && Check if final
    if isinstance(obj, list):
        items = enumerate(obj)
    elif isinstance(obj, dict):
        items = obj.items()
    elif '__dict__' in dir(obj):
        items = obj.__dict__.items()
    if not items:
        return stg + str(obj)

    # Recurse
    stg += '(' + type(obj).__name__ + ')\n'
    for k, v in items:
        stg += str_pretty(v, indent=indent, rec=rec+1, key=k) + "\n"

    # Return without empty lines
    return re.sub(r'\n\s*\n', '\n', stg)[:-1]

class C(object):
    def __init__(self):
        self.phn = 99009  # Number
        self.address = 'ABC'  # String
        self.my_list = [1, 2, 3, 4]  # List
        self.my_dic = {1:2, 3:4}  # Dictionary

obj_root = C()
obj_root.member1 = C()
print(str_pretty(obj_root, key='obj_root'))  # Key is optional, it informs the name of the variable
Cepheus answered 27/1, 2020 at 21:57 Comment(3)
not even working. TypeError: can't multiply sequence by non-int of type 'str'Condom
Too bad; would definitely like one that can handle when an object has a list of objects as one of its attributes/properties.Jeff
Thank for your comments. It works well for me, I made edits to put a full working example. It iterates well over list and dict of object.Cepheus

© 2022 - 2024 — McMap. All rights reserved.