0

I am working on something which should manage multiple water dispensers. I need to get some data from a json file and then load it into objects after that, append the objects to a list. For some reason list.append changes other object's parameters(more specific, location). Here is my code:

WaterDispenser.py

class WaterDispenser():
    def __init__(self, id: int = -1, status: bool = False, location: list=[-1, -1]) -> None:
        self.id = id
        self.status = status
        self.location = location
    
    def Dump(self) -> dict:
        """Dumps the propoerties in a json dictionary
        
        Returns
        -------
        dict
            A dictionary with a collection of propoerties and their names
        """
        
        return {"id": self.id, "status": self.status, "location":[self.location[0], self.location[1]]}

    def Load(self, object: dict) -> None:
        """Loads the json dictoinary in memory

        Parameters
        ----------
        object : dict, required
            The json file with the properties of the dispenser

        Returns
        -------
        None
        """
        
        self.id = object["id"]
        self.status = object["status"]
        self.location[0] = object["location"][0]
        self.location[1] = object["location"][1]
        return None

main.py

import json
from WaterDispenser import WaterDispenser

dispensers = []

def LoadDispensers(path: str = "dispensers.json") -> int:
    """Loads the json file in memory.

    Parameters
    ---------
    path : str, optional
        The path of the file to be loaded. Defaults to "dispensers.json".

    Returns
    -------
    int
        Count of dispensers data loaded
    """
    
    global dispensers
    
    dispensers = []
    
    data = json.load(open(path, "r"))

    for d in data:
        x = WaterDispenser()
        x.Load(d)
        dispensers.append(x)
        
    return len(dispensers)

if __name__ == '__main__':
    print(LoadDispensers())
    
    print([o.Dump() for o in dispensers])

dispensers.json

[
    {"id": 0, "status": true, "location": [0, 0]},
    {"id": 1, "status": true, "location": [0, 1]},
    {"id": 2, "status": false, "location": [1, 1]}
]

Output:

3
[{'id': 0, 'status': True, 'location': [1, 1]}, {'id': 1, 'status': True, 'location': [1, 1]}, {'id': 2, 'status': False, 'location': [1, 1]}]
Axo
  • 5
  • 2

1 Answers1

0

The functional answer:

Change the init of WaterDispenser to

from typing import Optional

class WaterDispenser():
    def __init__(self, id: int = -1, status: bool = False, location: Optional[list] = None) -> None:
        self.id = id
        self.status = status
        self.location = location or [-1, -1]

This should result in the expected response of

3
[{'id': 0, 'status': True, 'location': [0, 0]}, {'id': 1, 'status': True, 'location': [0, 1]}, {'id': 2, 'status': False, 'location': [1, 1]}]

The why:

Generally you want to avoid using mutable values as kwarg values because they're pre-computed (so your default location argument was technically the same object in memory across your WaterDispenser instances). append wasn't the culprit here and you can read more about this all via this SO discussion or read a succinct explanation via this answer to a similar question.

Design note:

It's worth noting that the way you are using Load in the above example could just be folded into WaterDispenser.__init__, so something like

from typing import Dict, Any

class WaterDispenser():
    def __init__(self, data: Dict[Any]) -> None:
        self.id = data.get("id", -1)
        self.status = data.get("status", False)
        self.location = data.get("location", [-1, -1])

or if you want to avoid typing

class WaterDispenser():
    def __init__(self, data: dict) -> None:
        self.id = data.get("id", -1)
        self.status = data.get("status", False)
        self.location = data.get("location", [-1, -1])

That example still includes your default values but if you removed the secondary arguments from those get calls you could protect against missing data at runtime without having to check to see if you had, say, an impossible location data point like [-1, -1].

Ezra Goss
  • 124
  • 8