1

I got a class Car that has an attribute called parts. This attribute is a list that will be filled in the process of my program. parts will contain a list other type of objects, like Engine, Battery, Wheels and so on, that have their own attributes but does not inherits from the Car. After I will build my Car object instance and fill its parts with all necessary objects, I would like convert it to dictionaries (easy to search and for comparison) and further to JSON. I found out how __dict__() works for custom objects and this makes it very quick and elastic without defining it in the Car class. But the __dict__ does not recursively creates dictionary for the objects inside the attribute parts.

How should I approach this problem? Should I define my own __dict__() method in class Car or there is another way, that will keep my classes elastic?

SOLUTION:

I have moved forward thanks to @enzo. He didn't put the right answer cause he didn't assumed, that the parts attribute of Car class is a list of parts. And sometimes, Car can have other attribute that is a list hat does not have __dict__ attribute. So there needs to be more checks.

Below my code. cars is a list of all Car objects, cars_to_dict will keep my current "snapshoot" of my cars list:

cars = list()
cars_to_dict = dict()
for car in cars:
  for k, v in car.__dict__.items():
    if type(v) == type([list()]): 
      cars_to_dict = cars_to_dict | { k : list() }
      for a in v:
        if type(a) == type(Whell()):
          cars_to_dict[k].append(a.__dict__)
        else:
          cars_to_dict[k].append(a)
    else:
      cars_to_dict = cars_to_dict | { k : list() }
plepleple
  • 15
  • 7
  • you don't need `__dict__` to compare objects. Consider dataclass instead. – njzk2 Jun 17 '21 at 20:39
  • `__dict__` **does not convert an object to a dictionary**. – juanpa.arrivillaga Jun 17 '21 at 21:08
  • "I would like convert it to dictionaries (easy to search and for comparison) " honestly this sounds like a bad idea, which entirely defeats creating the classes in the first place – juanpa.arrivillaga Jun 17 '21 at 21:09
  • @juanpa.arrivillaga: I know that I can just use `==` operator on both objects, but rather I would like to compare some of attributes that make sure that both are the same. Instead, should I override the `==` operator? – plepleple Jun 18 '21 at 05:34

2 Answers2

0

You can do something like this:

# Equivalent to your Engine class
class Foo:
    def __init__(self, value):
        self.v1 = value
        
# Equivalent to your Battery class
class Bar:
    def __init__(self, value):
        self.v2 = value
        
# Equivalent to your Wheels class
class Baz:
    def __init__(self, value):
        self.v3 = value
    
# Equivalent to your Car class
class FBB:
    def __init__(self):
        self.v = 0
        self.foo = Foo(1)
        self.bar = Bar(2)
        self.baz = Baz(3)
        
fbb = FBB()
print({k: vars(v) if hasattr(v, '__dict__') else v for k, v in vars(fbb).items()})
# {'v': 0, 'foo': {'v1': 1}, 'bar': {'v2': 2}, 'baz': {'v3': 3}}

Note that this won't work if Bar contains a reference to Baz, for example. For that, you may consider creating a recursive function:

class Foo:
    def __init__(self, value):
        self.v1 = value
        
class Bar:
    def __init__(self, value):
        self.v2 = value
        # Contains a reference to Baz!
        self.baz = Baz(3)
        
class Baz:
    def __init__(self, value):
        self.v3 = value
    
class FBB:
    def __init__(self):
        self.v = 0
        self.foo = Foo(1)
        self.bar = Bar(2)
        
def full_vars(obj):
    return {k: full_vars(v) if hasattr(v, '__dict__') else v for k, v in vars(obj).items()}
        
fbb = FBB()
print(full_vars(fbb))
# {'v': 0, 'foo': {'v1': 1}, 'bar': {'v2': 2, 'baz': {'v3': 3}}}
enzo
  • 9,861
  • 3
  • 15
  • 38
0

How about the below. I'm assuming single part type can appear only once in a car (you can generalize it to get multiple wheels for instance).

class Engine:
    def __init__(self, vol:int):
        self.vol = vol
        
class Wheel:
    def __init__(self, radius:int):
        self.radius = radius
    def __repr__(self):
        # consider maybe using json.dump instead of str (__repr__ has to return str)
        return str(self.__dict__)

class Car:
    def __init__(self, name:str, **kwargs):
        self.name = name
        self.parts = kwargs or dict()
        
    def add_part(self, part):
        self.parts[type(part).__name__] = part
    
    def remove_part(self, part):
        self.parts.pop(part.__name__, None)
        

x = Car("BMW")
x.add_part(Engine(50))
print(x.parts)
x.add_part(Wheel(35))
print(x.parts)
x.remove_part(Engine)
print(x.parts)

Outputs:

{'Engine': <__main__.Engine object at 0x7436868c70>}
{'Engine': <__main__.Engine object at 0x7436868c70>, 'Wheel': {'radius': 35}}
{'Wheel': {'radius': 35}}

Then

>>> print(x.__dict__)

{'name': 'BMW', 'parts': {'Wheel': {'radius': 35}}}

For more info about __repr__: https://stackoverflow.com/a/1984177/11610186

For more info about __dict__: https://stackoverflow.com/a/19907498/11610186

Grzegorz Skibinski
  • 12,624
  • 2
  • 11
  • 34
  • 1
    Thank you, this is what I am doing now. The question was: how to create a dictionary from object (`Whell`) that is inside another object (`Car`) invoking a method in the main object (`x.__dict__`). Your last code listing shows the Wheel object in the dictionary describing current state of `x` object, but I would like to make `x.__dict__` method to strip also all objects (`Whell`s and so on...) inside all attributes of `x`. – plepleple Jun 18 '21 at 05:54
  • What's the point of `type(part).__name__`? Why not just `type(part)`? – juanpa.arrivillaga Jun 18 '21 at 06:10
  • @juanpa.arrivillaga - try it, it's mostly about formatting (type name instead of ` – Grzegorz Skibinski Jun 18 '21 at 08:14
  • @plepleple - try now `__repr__` was the missing puzzle. – Grzegorz Skibinski Jun 18 '21 at 08:29
  • @GrzegorzSkibinski Who cares about how it prints? `type(part)` would be less brittle – juanpa.arrivillaga Jun 18 '21 at 17:49