1

I have 2 functions, apply_rule and match. Each is defined independently - they don't belong to any class.

match takes two required paramters, and an optional parameter, called pairs. It defaults to an empty dictionary. Below is the code for apply_rule and for match. Note that match is called, and the optional parameter pairs isn't specified.

def apply_rule(pat, rule):
  if not isrule(rule):
    return "Not a valid rule"

  subs = match(lhs(rule), pat)
  if subs == {}:
    return pat
  else:
    return substitute(rhs(rule), subs)


def match(pat, lst, pairs={}):
  if pat == [] and lst == []:
    return pairs
  elif isvariable(pat[0]):
    if pat[0] not in pairs.keys():
      pairs[pat[0]] = lst[0]
    elif pat[0] in pairs.keys() and lst[0] != pairs[pat[0]]:
      return False
  elif pat[0] != lst[0]:
    return False
  return match(pat[1:], lst[1:], pairs) 

Right now, the unittest for match fails, because it "remembers" pairs, as defined in the test for apply_rule.

However, if I change the 3rd line in apply_rule to subs = match(lhs(rule), pat, {})then the tests pass. Do you know why? As far as I can tell, there shouldn't be any way that match remembers the value of pairs from when it was called in other tests.

Below are the unit tests, for reference.

def test_match(self):
    self.assertEqual({}, match(['a', 'b', 'c'], ['a', 'b', 'c']))

    self.assertEqual(self.dict_with_x, match(['a', '_X', 'c'], ['a', '5', 'c']))
    self.assertEqual(self.dict_with_x, match(self.pat_with_xx, self.list_with_55))

    self.assertEqual(self.dict_with_xy, match(self.pat_with_xy, self.list_with_5hi))

    self.assertFalse(match(self.pat_with_xx, ['a', 'b', 'c', 'd']))
    self.assertFalse(match(['a', 'b', 'c'], ['a', 'b', 'd']))

def test_apply_and_firerule(self):
    pattern1 = "my mother thinks I am fat".split(' ')
    expected = "do you think you are fat ?".split(' ')
    self.assertEqual(apply_rule(pattern1, self.r1), expected)

And the failure message...

Traceback (most recent call last):
  File "pattern_matcher_tests.py", line 65, in test_match
    self.assertEqual({}, match(['a', 'b', 'c'], ['a', 'b', 'c']))
AssertionError: {} != {'_Y': 'fat', '_X': 'mother'}
- {}
+ {'_X': 'mother', '_Y': 'fat'}
Ben Hymers
  • 25,586
  • 16
  • 59
  • 84
bobbypins
  • 134
  • 2
  • 8
  • 1
    http://stackoverflow.com/questions/1132941/least-astonishment-in-python-the-mutable-default-argument http://effbot.org/zone/default-values.htm – dm03514 Feb 13 '13 at 00:41

1 Answers1

1

from effbot

Why does this happen? #

Default parameter values are always evaluated when, and only when, the “def” statement they belong to is executed; see:

http://docs.python.org/ref/function.html

for the relevant section in the Language Reference.

What to do instead? #

The workaround is, as others have mentioned, to use a placeholder value instead of modifying the default value. None is a common value:

dm03514
  • 54,664
  • 18
  • 108
  • 145