2

I have very large settings for multiple applications and I want to print this as multi line string. Let me give example (simplified) and explain want I want to achieve and what I get. I think about use of some package to get such nice formatting.

I assume that constructors arguments are same to __dict__ or __slots__ - if not __dict__ or __slots__ is more important to show.

I wrote some formatting library for single line but maybe is better solution with multiline output and more options.

Update (important):

Please not suggest to customize __repr__ or __str__ - I can not or do not want to customize hundred of classes (especially from third party libraries).

class Endpoint:
    def __init__(self, host_or_ip: str, port: int):
        self.host_or_ip = host_or_ip
        self.port = port


class ConnectionSettings:
    def __init__(self, endpoints: list[Endpoint]):
        self.endpoints = endpoints


class FarmSettings:
    def __init__(self, name: str, connection_settings: ConnectionSettings):
        self.name = name
        self.connection_settings = connection_settings


def main():
    settings = FarmSettings(
        name='alfa',
        connection_settings=ConnectionSettings(
            endpoints=[
                Endpoint('localhost', 80)
            ]
        )
    )

    print(settings)
    
    # what I get from default __repr__
    #
    # <__main__.FarmSettings object at 0x00000203EF713AF0>
    #
    # what do I want from some method
    # FarmSettings(
    #     name='alfa',
    #     connection_settings=ConnectionSettings(
    #         endpoints=[
    #             Endpoint(name='localhost', port=80)
    #         ]
    #     )
    # )


if __name__ == '__main__':
    main()
martineau
  • 119,623
  • 25
  • 170
  • 301
Chameleon
  • 9,722
  • 16
  • 65
  • 127
  • You could just define a `__repr__` for your class to return that string and `print(settings)` will work the way you want. Making it a multiline string is trivial - Add a `\n` character wherever you want a newline – Pranav Hosangadi Jul 12 '22 at 15:01
  • Does this answer your question? [How to print instances of a class using print()?](https://stackoverflow.com/questions/1535327/how-to-print-instances-of-a-class-using-print) – Pranav Hosangadi Jul 12 '22 at 15:01
  • This do not solve problem - I know how to write custom \_\_repr__ and \_\_str__. I do not want to write \_\_repr__ for hundred of classes (sometime you can not since classes are from third party libraries). It is not related to \_\_repr__ or \_\_str__ at all. Solution is need for any class without modification this class. – Chameleon Jul 12 '22 at 15:24
  • @PranavHosangadi it is not such simple. It is also not duplicate. I am very skilled programmer with expert Python understanding (10 years or more). – Chameleon Jul 12 '22 at 15:26
  • You can't do what you want generically. To change the output in the manner you desire requires knowledge of the internals of the particular class being reformatted — and as you said you don't want to do this for large numbers of classes. – martineau Jul 12 '22 at 15:47
  • @PranavHosangadi Not good idea to replace \_\_repr__ you can mess code - it can lead to bugs. Better is to traverse object tree to print result but it need to implement some code but cheaper than custom \_\_repr__. – Chameleon Jul 12 '22 at 15:48
  • @martineau That is true that there is no generic backward transformations to constructors but assume that it almost possible with some exceptions. See pprint it works for dict, list, set - same we can do here for object but with some exceptions. imprefect solutions are also good if no perfect solution. – Chameleon Jul 12 '22 at 15:51
  • Probably not relevant if your classes are already built, but I found that nice prints, esp on nesteds, were one benefit from using Pydantic classes. Which do very well in configuration contexts . – JL Peyret Jul 12 '22 at 18:12

2 Answers2

3

You could use e.g. __dict__ to recursively transform your objects to a dictionary, and then use pprint.pprint or json.dumps to pretty-print this dictionary:

def dictify(obj):
    if isinstance(obj, (int, float, str, bool)):
        return obj
    if isinstance(obj, (list, tuple)):
        return list(map(dictify, obj))
    return {"_type": type(obj).__name__,
            **{k: dictify(v) for k, v in obj.__dict__.items()}}
    

settings = FarmSettings(
    name='alfa',
    connection_settings=ConnectionSettings(
        endpoints=[
            Endpoint('localhost', 80)
        ]
    )
)

import pprint
pprint.pprint(dictify(settings))

Sample output:

{'_type': 'FarmSettings',
 'connection_settings': {'_type': 'ConnectionSettings',
                         'endpoints': [{'_type': 'Endpoint',
                                        'host_or_ip': 'localhost',
                                        'port': 80}]},
 'name': 'alfa'}

Note: The shown function is rudimentary at best. It does not cover many cases of attribute values (e.g. dict, let alone any kinds of more exotic classes), and it also does not handle cyclic references in your model (i.e. it will run into endless recursion in that case).

tobias_k
  • 81,265
  • 12
  • 120
  • 179
  • I looks like the best solution (it not need to be perfect). I try to implement like this. If not better solution I will accept. I do similar solution for serialization of Python object with msgpack :) Simple and look great. – Chameleon Jul 12 '22 at 15:55
  • You can have also some \_\_slots__ but it not need to show this in examples whatever worth to remember when it need to be implemented :) – Chameleon Jul 12 '22 at 16:12
  • Upvoted but I’d also expect hassles around properties. – JL Peyret Jul 12 '22 at 18:16
  • I create encoder which transform into YAML, Pretty format. – Chameleon Jul 12 '22 at 18:38
0

Adding the __str__ (or __repr__) method to your class will allow you to overwrite the output of print(<insert complex object here>). For example:

def __str__(self):
   return self.<attribute> + ...

So, for example, if you had a class like this:

class Person:
   def __init__(self, name):
      self.name = name
   def __str__(self):
      return self.name

print(Person("Ryan"))

You get the output:

Ryan
Ryan
  • 1,081
  • 6
  • 14
  • This do not solve problem - I know how to write custom \_\_repr__ and \_\_str__. I do not want to write \_\_repr__ for hundred of classes (sometime you can not since classes are from third party libraries). It is not related to \_\_repr__ or \_\_str__ at all. Solution is need for any class without modification this class. – Chameleon Jul 12 '22 at 15:23