1

I am trying to write a program which contains a class called compound. A basic form of this class is here;

class Compound:
    def __init__(self, name="", entropy=0, enthalpy=0, constituents=[]):
        self.constituents = constituents
        self.name = name
        self.entropy = entropy
        self.enthalpy = enthalpy
        self.check_data()
        if (name != ""):
            self.find_constituents()
    def find_constituents(self):
        name = self.name
        if (len(name) == 0):
            return 0
        name = re.sub(r'\([a-z]+\)', '', name)
        p = re.findall(r'\d+', name[0])
        if (len(p) > 0):
            name = name[1:]
            lop = int(p[0])
        else:
            lop = 1
        chem = re.findall('[A-Z][a-z0-9]{0,3}', name)
        for p in chem:
            l = re.findall(r'\d+', p)
            if (len(l) == 1):
                num = int(l[0])
            else:
                num = 1
            q = re.sub(r'\d+', '', p)
            print q
            try:
                for l in range(0, num):
                    for ki in range(0, lop):
                        self.constituents.append(q)
            except:
                print("Element "+q+" not found.")

In essence, all it does is take the name as input, and if the name is not "" it then tries to break it down to components and put these into constituents - so, F2 will put [F, F] in constituents, NaOH will put [Na, O, H] and so on.

When running this with a single Compound, it works perfectly;

>>> a = Compound("F2")
Data not found
F
>>> print vars(a)
{'constituents': ['F', 'F'], 'entropy': 0, 'name': 'F2', 'enthalpy': 0}

When I run it again, but before printing I assign another variable, b, to a different compound;

>>> a = Compound("F2")
Data not found
F
>>> b = Compound("NaOH")
Data not found
Na
O
H
>>> print vars(a)
{'constituents': ['F', 'F', 'Na', 'O', 'H', 'F', 'F', 'Na', 'O', 'H'], 'entropy': 0, 'name': 'F2', 'enthalpy': 0}

It has for some reason changed the value of a.constituents in this reassignment. I have no idea why this is as it is. From what I can see, given that the other values (name for instance) have not changed it would imply it is within the find_constituents() part. Given, however, that find_constituents does not use any external variables I cannot see why this would be happening.

Could anyone lend any light to the problem? Thanks!

user1150512
  • 259
  • 1
  • 9
  • Hi, I had a look on the page and it seems to be the same issue - getting rid of the default values in the __init__() function fixed it. Thank you! – user1150512 Nov 08 '15 at 16:33
  • Yes, that is, unfortunately, a classic mistake. Never use mutable objects (lists, dicts) as default values for functions. They persist across calls. – Keith Nov 08 '15 at 16:54

1 Answers1

2

Your default value for constituents, [], is shared across all calls to the constructor. I suggest simply copying the list when assigning constituents to self:

self.constituents = constituents[:]

That way you're safe no matter what's passed in.

melpomene
  • 84,125
  • 8
  • 85
  • 148
Tom Karzes
  • 22,815
  • 2
  • 22
  • 41
  • That's potentially inefficient; the `None` default value is generally a better idea. – jonrsharpe Nov 08 '15 at 17:06
  • @johnrsharpe Well, it's less error prone, and in this case I don't think it's a problem. Of course, it could do both: Change `None` into a new `[]` and copy if it's already a list. – Tom Karzes Nov 08 '15 at 17:58