troy_s
troy_s

Reputation: 142

Use __str__ of each instance attribute for a class's __str__

Is there a way in Python 3 to recursively call private member variables unique __str__ functions programmatically? Something like:

class A:
  def __str__(self):
    return "A"

class B:
  def __str__(self):
    return "B"

class C:
  def __init__(self):
    self._A = A()
    self._B = B()

  def __str__(self):
    for var in vars(self):
      return str(var)

When calling the individual private members, it works fine. Would like a method to do it dynamically however.

Thanks so much.

Upvotes: 4

Views: 5701

Answers (3)

o11c
o11c

Reputation: 16056

Are you sure you shouldn't be using __repr__?

Anyway, here's an example using attrs, beacuse I'm stuck on Python 3.5. With dataclasses it will work in a similar manner.

import attr


class A:
    def __str__(self):
        return 'A'

class B:
    def __str__(self):
        return 'B'

@attr.s
class C:
    a = attr.ib(default=attr.Factory(A))
    b = attr.ib(default=attr.Factory(B))


if __name__ == '__main__':
    c = C()
    print(c) # __str__ defaults to __repr__

    def __str__(self):
        bits = ['<C']
        for a in self.__attrs_attrs__:
            bits.append(' %s=%s' % (a.name, getattr(self, a.name)))
        bits.append('>')
        return ''.join(bits)
    C.__str__ = __str__
    print(c) # custom __str__

Upvotes: 0

Brad Solomon
Brad Solomon

Reputation: 40878

You can also use the dictionary keys; vars() is self.__dict__:

>>> class A:
...     def __str__(self):
...         return self.__class__.__name__
... 
>>> class B:
...     def __str__(self):
...         return self.__class__.__name__
... 
>>> str(A())
'A'
>>> repr(A())  # "long-form" the hex-string is id()
'<__main__.A object at 0x10f65a908>'

>>> class C:
...     def __init__(self):
...        self.A = A()
...        self.B = B()
...     def __str__(self):
...         return '\n'.join(self.__dict__)
... 
>>> C()
<__main__.C object at 0x10f65aa58>
>>> print(C())  # Uses str(C())
A
B

vars(self) is effectively self. In turn, self.__dict__ is a dict used to store an object’s (writable) attributes.

>>> C().__dict__
{'A': <__main__.A object at 0x10f65aa90>, 'B': <__main__.B object at 0x10f65aac8>}

The signature is '\n'.join(iterable), and when you iterate over a dictionary, you iterate over its keys, which suffices in this case.

Note on Dataclasses

I'm not totally sure if (Python 3.7+) dataclasses are an easier solution here. That's because they automatically implement a __repr__() but not a __str__() as far as I can tell:

>>> from dataclasses import dataclass
>>>
>>> @dataclass
... class C:
...     _A: object = A()
...     _B: object = B()
... 
>>> c = C()  # still uses repr() for each field
>>> str(c)
'C(_A=<__main__.A object at 0x10f373828>, _B=<__main__.B object at 0x10f373940>)'

In other words, you'd need to replace A.__str__ with A.__repr__ (same for B, which is maybe not something you want to do in the first place with regards to those two classes.)

Upvotes: 1

kungfushark
kungfushark

Reputation: 166

The vars function returns a dictionary where the keys are the variable names (as strings) and the values are the values of the variable. So iterating over the values should work.

class A:
  def __str__(self):
    return "A"

class B:
  def __str__(self):
    return "B"

class C:
  def __init__(self):
    self._A = A()
    self._B = B()

  def __str__(self):
    output = ""
    for _,var in vars(self).items(): #Iterate over the values
      output += str(var) #Use the str() function here to make the object return its string form
    return output #Need to return instead of print since that is what the __str__() function should do

You can add some kind of separator (like a \n) between the values if you want. Just replace str(var) with str(var) + "\n".

Upvotes: 3

Related Questions