pythondictionarysetdefault

dict.setdefault() is used when value already exists


I'm trying to add new keys to the existing dictionary, setting as default value, the value of the previous key.

def check_data(end, start, profile):
    print(profile)

    for day in range((end-start).days+1):
        profile.setdefault(start+datetime.timedelta(day),
           {'expediture':0, 'top-up':0,
           'budget':profile[start+datetime.timedelta(day-1)]['budget'] })

start  = datetime.date(2019,2,1)
end    = datetime.date(2019,2,17)
check_data(end, start, month_data)

output from print(profile):

    {datetime.date(2019, 2, 1): {'expediture': 0, 'top-up': 0, 'budget': 100.0}}

 ---------------------------------------------------------------------------
    KeyError                                  Traceback (most recent call last)
    <ipython-input-4-e44462627692> in <module>
    ----> 1 check_data(end, start, month_data)

    <ipython-input-2-90b936150b04> in check_data(end, start, profile)
         20         profile.setdefault(start+datetime.timedelta(day),
         21                                     {'expediture':0, 'top-up':0,
    ---> 22
       'budget':profile[start+datetime.timedelta(day-1)]['budget'] })
        23
        24 def add_money(profile, topup, date=datetime.date.today()):

       KeyError: datetime.date(2019, 1, 31)

I don't understand why setdefault() tries to set default value to datetime.date(2019, 2, 1) if this value already exists.

I could fix this problem with if but I'd like to understand how setdefault works and maybe there is alternative solution for this problem.


Solution

  • Consider how you would write this code without setdefault (and some minor refactoring for readability):

    def check_data(end, start, profile):
        print(profile)
    
        for day in range((end-start).days+1):
            yesterday = start + datetime.timedelta(day - 1)
            today = start + datetime.timedelta(day)
            profile[today] = {
                'expenditure': 0,
                'top-up': 0,
                'budget': profile[yesterday]['budget']
            }
    
    start  = datetime.date(2019,2,1)
    end    = datetime.date(2019,2,17)
    check_data(end, start, month_data)
    

    On the first iteration of the loop, you need the value of profile[datetime(2019,1,31)] in order to set the value of profile[datetime(2019,2,1)], but that value was never set.

    The only use for setdefault here would be with the key yesterday, assuming you could start with some generic profile. For simplicity, I'll assume that budget is also an integer.

    def check_data(end, start, profile):
        print(profile)
    
        for day in range((end-start).days+1):
            yesterday = start + datetime.timedelta(day - 1)
            today = start + datetime.timedelta(day)
            profile[today] = {
                'expenditure': 0,
                'top-up': 0,
                'budget': profile.setdefault(yesterday, {'budget': 0})['budget']
            }
    

    Now, if profile[yesterday] doesn't already exist, setdefault will execute the equivalent of profile[yesterday] = {'budget': 0} before returning that value.