tony tony - 7 months ago 7
Python Question

Function modifies inputs when not intended

I have 3 lists:

years = [2013,2014,2015,2016,2017,2018]
GamsVars = ['scen_name','timefile']
settings = ['ScenarioA','s']


I have a function meant to return a list exactly like settings but with years appended to the relevant entries:

def AppendYearToSettings(year,GamsVarsIn,SettingsIn):
SettingsOut = SettingsIn
SettingsOut[GamsVarsIn.index('scen_name')] = SettingsIn[GamsVarsIn.index('scen_name')] + "\\" + str(year)
SettingsOut[GamsVarsIn.index('timefile')] = SettingsIn[GamsVarsIn.index('timefile')] + str(year)
return(SettingsOut)


When I test out the function in a loop:

for y in years:
ysettings = AppendYearToSettings(y,GamsVars,settings)
print(ysettings)


My settings is appending years cumulatively:

['ScenarioA\\2013', 's2013']
['ScenarioA\\2013\\2014', 's20132014']
['ScenarioA\\2013\\2014\\2015', 's201320142015']
['ScenarioA\\2013\\2014\\2015\\2016', 's2013201420152016']
['ScenarioA\\2013\\2014\\2015\\2016\\2017', 's20132014201520162017']
['ScenarioA\\2013\\2014\\2015\\2016\\2017\\2018', 's201320142015201620172018']


I've tried explicitly preventing settings (the original settings) from being modified, but it seems that my function is somehow modifying settings.

What is the reason that is causing this problem?

Answer

What is the reason that is causing this problem?

When you are doing SettingsOut = SettingsIn, you are only creating another reference to the object referenced by SettingsIn. If you want to make a copy of the list, you can do this:

SettingsOut = SettingsIn[:]

Or, you can use copy.deepcopy (you need to import copy to use this). You need this only when the elements of the list are themselves references, and you want to create new copies of the objects as well. Take a look at the comment by John Y below.

SettingsOut = copy.deepcopy(SettingsIn)

Check this out:

# shallow copy
>>> l1 = [1, 2, 3, 4, 5]
>>> l2 = l1
>>> l2 is l1
True
>>> id(l1)
24640248
>>> id(l2)
24640248

# Deep copy
>>> l2 = l1[:]
>>> l2 is l1
False
>>> id(l2)
24880432
>>> id(l1)
24640248
>>>

So your function could be like this:

def AppendYearToSettings(year,GamsVarsIn,SettingsIn):
    SettingsOut = SettingsIn[:]
    SettingsOut[GamsVarsIn.index('scen_name')] = SettingsIn[GamsVarsIn.index('scen_name')] + "\\" + str(year)
    SettingsOut[GamsVarsIn.index('timefile')] = SettingsIn[GamsVarsIn.index('timefile')] + str(year)
    return SettingsOut

for y in years:
    ysettings = AppendYearToSettings(y, GamsVars, settings)
    print(ysettings)

Output:

['ScenarioA\\2013', 's2013']
['ScenarioA\\2014', 's2014']
['ScenarioA\\2015', 's2015']
['ScenarioA\\2016', 's2016']
['ScenarioA\\2017', 's2017']
['ScenarioA\\2018', 's2018']

A little astray this, but you should take a look at the PEP style guide for function naming ;-) It says:

Function Names

Function names should be lowercase, with words separated by underscores as necessary to improve readability.

mixedCase is allowed only in contexts where that's already the prevailing style (e.g. threading.py), to retain backwards compatibility.