Reputation: 142
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
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
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.
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
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