Anton
Anton

Reputation: 1538

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 ysettings 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?

Upvotes: 1

Views: 74

Answers (1)

Sнаđошƒаӽ
Sнаđошƒаӽ

Reputation: 17592

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:

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

>>> 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.

Upvotes: 2

Related Questions