How to use pprint to print an object using the built-in __str__(self) method?

42.6k views Asked by At

I have a Python script which processes a .txt file which contains report usage information. I'd like to find a way to cleanly print the attributes of an object using pprint's pprint(vars(object)) function.

The script reads the file and creates instances of a Report class. Here's the class.

class Report(object):
    def __init__(self, line, headers):
        self.date_added=get_column_by_header(line,headers,"Date Added")
        self.user=get_column_by_header(line,headers,"Login ID")
        self.report=get_column_by_header(line,headers,"Search/Report Description")
        self.price=get_column_by_header(line,headers,"Price")
        self.retail_price=get_column_by_header(line,headers,"Retail Price")

    def __str__(self):
        from pprint import pprint
        return str(pprint(vars(self)))

I'd like to be able to print instances of Report cleanly a-la-pprint.

for i,line in enumerate(open(path+file_1,'r')):
    line=line.strip().split("|")
    if i==0:
        headers=line

    if i==1:
        record=Report(line,headers)
        print record

When I call

print record

for a single instance of Report, this is what I get in the shell.

{'date_added': '1/3/2012 14:06',
'price': '0',
'report': 'some_report',
'retail_price': '0.25',
'user': 'some_username'}
 None

My question is two-fold.

First, is this a good / desired way to print an object's attributes cleanly? Is there a better way to do this with or without pprint?

Second, why does

None

print to the shell at the end? I'm confused where that's coming from.

Thanks for any tips.

6

There are 6 answers

1
Dan On BEST ANSWER

pprint is just another form of print. When you say pprint(vars(self)) it prints vars into stdout and returns none because it is a void function. So when you cast it to a string it turns None (returned by pprint) into a string which is then printed from the initial print statement. I would suggest changing your print to pprint or redefine print as print if its all you use it for.

def __str__(self):
    from pprint import pprint
    return str(vars(self))

for i,line in enumerate(open(path+file_1,'r')):
    line = line.strip().split("|")
    if i == 0:
        headers = line
    if i == 1:
        record = Report(line,headers)
        pprint record

One alternative is to use a formatted output:

def __str__(self):
    return "date added:   %s\nPrice:        %s\nReport:       %s\nretail price: %s\nuser:         %s" % tuple([str(i) for i in vars(self).values()])

Hope this helped

0
Ismail Badawi On

pprint.pprint doesn't return a string; it actually does the printing (by default to stdout, but you can specify an output stream). So when you write print record, record.__str__() gets called, which calls pprint, which returns None. str(None) is 'None', and that gets printed, which is why you see None.

You should use pprint.pformat instead. (Alternatively, you can pass a StringIO instance to pprint.)

0
Charles Merriam On

Dan's solution is just wrong, and Ismail's in incomplete.

  1. __str__() is not called, __repr__() is called.
  2. __repr__() should return a string, as pformat does.
  3. print normally indents only 1 character and tries to save lines. If you are trying to figure out structure, set the width low and indent high.

Here is an example

class S:
    def __repr__(self):
        from pprint import pformat
        return pformat(vars(self), indent=4, width=1)

a = S()
a.b = 'bee'
a.c = {'cats': ['blacky', 'tiger'], 'dogs': ['rex', 'king'] }
a.d = S()
a.d.more_c = a.c

print(a)

This prints

{   'b': 'bee',
    'c': {   'cats': [   'blacky',
                         'tiger'],
             'dogs': [   'rex',
                         'king']},
    'd': {   'more_c': {   'cats': [   'blacky',
                               'tiger'],
                  'dogs': [   'rex',
                              'king']}}}

Which is not perfect, but passable.

0
cdunn2001 On

For pretty-printing objects which contain other objects, etc. pprint is not enough. Try IPython's lib.pretty, which is based on a Ruby module.

from IPython.lib.pretty import pprint
pprint(complex_object)
0
Bill On

@Anyany Pan way is the best.

Here I share a real case, when I deal with Azure resource

in AWS resources, I can use pprint to print the resource detail easily, but it doesn't work with Azure resource. Because they are different types.

from azure.identity import AzureCliCredential
from azure.mgmt.compute import ComputeManagementClient
#from pprint import pprint
from beeprint import pp
import os

# Acquire a credential object using CLI-based authentication.
credential = AzureCliCredential()

# Retrieve subscription ID from environment variable.
subscription_id = os.environ["AZURE_SUBSCRIPTION_ID"]

compute_client = ComputeManagementClient(credential, subscription_id)
vm_list = compute_client.virtual_machines.list_all()

for vm in vm_list:
  type(vm)
  # pprint(vm) # doesn't work for Azure resource
  pp(vm)

output for reference by beeprint

<class 'azure.mgmt.compute.v2020_12_01.models._models_py3.VirtualMachine'>
instance(VirtualMachine):
  _attribute_map: {
    'additional_capabilities': {
      'key': 'properties.additionalCapabilities',
      'type': 'AdditionalCapabilities',
    },
    'availability_set': {
      'key': 'properties.availabilitySet',
      'type': 'SubResource',
    },
    'billing_profile': {
      'key': 'properties.billingProfile',
...

output by pprint

<class 'azure.mgmt.compute.v2020_12_01.models._models_py3.VirtualMachine'>
<azure.mgmt.compute.v2020_12_01.models._models_py3.VirtualMachine object at 0x1047cf4f0>
<class 'azure.mgmt.compute.v2020_12_01.models._models_py3.VirtualMachine'>
<azure.mgmt.compute.v2020_12_01.models._models_py3.VirtualMachine object at 0x1047cf5b0>
0
Anyany Pan On

I think beeprint is what you need.

Just pip install beeprint and change your code to:

def __str__(self):
    from beeprint import pp
    return pp(self, output=False)