1

Doing a self-study of Python via MIT Open Courseware, and ran into a problem with this bit of code below. When I run this function either alone or within another function, it mutates the originally passed value 'hand', and I am not sure why. I set two local variables (hand0 and tester) to hand, the first to preserve the initial value, and the second to iterate over. However, all three change, while I'm only expecting 'tester' to do so. Other than mutating 'hand', the function works as expected.

(Values as passed to the function vary within set parameters: word_list is a list of valid English words, word is a string that I replace within this function for testing, and hand is a dictionary of letters and their associated counts. Debugging code commented out.)

def is_valid_word(word, hand, word_list):
    """
    Returns True if word is in the word_list and is entirely
    composed of letters in the hand. Otherwise, returns False.
    Does not mutate hand or word_list.

    word: string
    hand: dictionary (string -> int)
    word_list: list of lowercase strings
    """
    hand0 = hand
    tester = hand
    #display_hand(hand)
    #display_hand(tester)
    word = raw_input('test word: ')
    length = len(word)
    disc = True
    for c in range(length):
        if word[c] in tester.keys() and tester[word[c]]>0:
            #print tester[word[c]]
            #display_hand(hand)
            #display_hand(tester)
            tester[word[c]]=tester[word[c]]-1            
        else:
            #print 'nope'
            disc = False
    if word not in word_list:
        disc = False
    #print disc
    #display_hand(hand)
    #display_hand(tester)
    #display_hand(hand0)
    return disc
tacaswell
  • 84,579
  • 22
  • 210
  • 199

2 Answers2

10

When you do tester = hand, you're only creating a new reference to the hand object. In other words, tester and hand are the same object. You could see this if you checked their id:

print id(tester)
print id(hand)  #should be the same as `id(tester)`

Or equivalently, compare with the is operator:

print tester is hand  #should return `True`

To make a copy of a dictionary, there is a .copy method available:

tester = hand.copy()
mgilson
  • 300,191
  • 65
  • 633
  • 696
  • 1
    Great minds think alike! It's amazing that our answers start with the exact same wording. – Mark Ransom Jan 30 '13 at 04:43
  • @MarkRansom -- I'd be happy to have my mind considered along side yours :-) – mgilson Jan 30 '13 at 04:44
  • Thanks for taking the time to answer my newbie question. Appreciate the help. – Phil Saulnier Jan 30 '13 at 04:49
  • @PhilSaulnier They have become great by helping newbies like you – Mirage Jan 30 '13 at 05:07
  • @PhilSaulnier -- This is actually one of the more difficult concepts that I've found that people have in python. Once you've got it figured out, it makes a lot of sense and is extremely useful/powerful, but it takes a little getting used to coming from other languages. – mgilson Jan 30 '13 at 05:14
5

When you do tester = hand you're not making a copy of hand, you're making a new reference to the same object. Any modifications you make to tester will be reflected in hand.

Use tester = hand.copy() to fix this: http://docs.python.org/2/library/stdtypes.html#dict.copy

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622