I am trying to make a programme that allows you to perfrom various forms of chemical analysis on some user defined molecule. So far, I have defined a Molecule object with a coordinate property associated with it. I set this to generate an Atom object of some sort on when the user sets the coordinate property to some list that descibes an molecule. However, the coordinate property only corrosponds to a list of atom objects when I reset it following instantiation of the molecule object. When I pass a list of coordinates on instantiation of the object, the property remains as the list of strings (same as input), and does not corrospond to a list of the corrosponding atom objects. The corrosponding code is,
periodic_table = {
    'H': {'Z': 1.008, 'nelec': 1},
    'C': {'Z': 12.011, 'nelec': 6},
    'N': {'Z': 14.007, 'nelec': 7},
    'O': {'Z': 15.999, 'nelec': 8},
    'S': {'Z': 32.06, 'nelec': 16},
    'Br': {'Z': 79.904, 'nelec': 35},
    'I': {'Z': 126.90, 'nelec': 53}
}
class Atom:
    def __init__(self, label: str, vector: list):
        self.__label = label
        self.vector = vector
        self.__lookup_info()
    def __lookup_info(self):
        atom_info = periodic_table[self.__label]
        self.__Z = atom_info['Z']
        self.__nelec = atom_info['nelec']
    @property
    def label(self):
        return self.__label
    @label.setter
    def label(self, value):
        if type(value) != str:
            raise Exception("Atomic labels must be a string")
        else:
            value = value.upper()
            self.__label = value
            self.__lookup_info()
            # lookup info and set z/nelec
    @property
    def Z(self):
        return self.__Z
    @property
    def nelec(self):
        return self.__nelec
class Molecule(object):
    def __init__(self, coordinates=None):
        self.__coordinates = coordinates
        self.nelec = None
        self.Z = None
        self.natom = None
    @property
    def coordinates(self):
        return self.__coordinates
    @coordinates.setter
    def coordinates(self, coords: list):
        depth = lambda l: isinstance(l, list) and max(map(depth, l)) + 1
        if depth(coords) != 2:
            raise Exception("Coordinates must be provided as a nested list of depth 2")
        else:
            atom_list = []
            for atom_coord in coords:
                print("Test")
                atom_coord = self.__check_coord(atom_coord)
                atom_label, atom_vec = atom_coord[0], atom_coord[1:]
                atom_list.append(Atom(atom_label, atom_vec))
            self.__coordinates = atom_list
    @staticmethod
    def __check_coord(coord):
        isnumber = lambda x: (type(x) == float or type(x) == int)
        if type(coord[0]) != str:
            raise Exception("First element must a string for an atomic label")
        if len(list(filter(isnumber, coord))) != 3 and len(coord) != 4:
            raise Exception("Each coordinate must be a list of length 4 with 1 string and 3 numbers")
        coord[0] = coord[0].upper()
        return coord
which yields the following outputs,
>>> mol = Molecule([['H', 0, 1, 1], ['O', -1, 0, 0], ['H', -1, 0, 1]])
>>> mol.coordinates
    [['H', 0, 1, 1], ['O', -1, 0, 0], ['H', -1, 0, 1]]
>>> mol2 = Molecule()
>>> mol2.coordinates =[['H', 0, 1, 1], ['O', -1, 0, 0], ['H', -1, 0, 1]]
    Test
>>> mol2.coordinates
    [<__main__.Atom object at 0x10c62c850>, <__main__.Atom object at 0x10c62c640>, <__main__.Atom object at 0x10c62c910>]
How can I achieve the behaviour where in both the case of the list being passed on init of the object or when the coordinated property is set after, the coordinates are set to a list of atom objects?
 
    