Skip to content

Commit 4dd83e4

Browse files
Merge pull request #782 from Axelrod-Python/779
Fixes the cache bug.
2 parents 1f4f2d7 + 87cc6d5 commit 4dd83e4

File tree

8 files changed

+77
-17
lines changed

8 files changed

+77
-17
lines changed

axelrod/deterministic_cache.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,24 @@ def __init__(self, file_name=None):
4646
if file_name is not None:
4747
self.load(file_name)
4848

49+
def _key_transform(self, key):
50+
"""
51+
Parameters
52+
----------
53+
key: tuple
54+
A 3-tuple: (player instance, player instance, match length)
55+
"""
56+
return key[0].name, key[1].name, key[2]
57+
58+
def __delitem__(self, key):
59+
return UserDict.__delitem__(self, self._key_transform(key))
60+
61+
def __getitem__(self, key):
62+
return UserDict.__getitem__(self, self._key_transform(key))
63+
64+
def __contains__(self, key):
65+
return UserDict.__contains__(self, self._key_transform(key))
66+
4967
def __setitem__(self, key, value):
5068
"""Overrides the UserDict.__setitem__ method in order to validate
5169
the key/value and also to set the turns attribute"""
@@ -60,7 +78,7 @@ def __setitem__(self, key, value):
6078
raise ValueError(
6179
'Value must be a list with length equal to turns attribute')
6280

63-
UserDict.__setitem__(self, key, value)
81+
UserDict.__setitem__(self, self._key_transform(key), value)
6482

6583
def _is_valid_key(self, key):
6684
"""Validate a proposed dictionary key
@@ -85,8 +103,8 @@ def _is_valid_key(self, key):
85103
# integer
86104
try:
87105
if not (
88-
issubclass(key[0], Player) and
89-
issubclass(key[1], Player) and
106+
isinstance(key[0], Player) and
107+
isinstance(key[1], Player) and
90108
isinstance(key[2], int)
91109
):
92110
return False

axelrod/match.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def __init__(self, players, turns, game=None, deterministic_cache=None,
3737
"""
3838
self.result = []
3939
self.turns = turns
40-
self._cache_key = (players[0].__class__, players[1].__class__, turns)
40+
self._cache_key = (players[0], players[1], turns)
4141
self.noise = noise
4242

4343
if game is None:

axelrod/strategies/memoryone.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@ def __init__(self, four_vector=None, initial=C):
4747
"""
4848
Player.__init__(self)
4949
self._initial = initial
50-
if four_vector:
50+
if four_vector is not None:
5151
self.set_four_vector(four_vector)
52+
if self.name == 'Generic Memory One Player':
53+
self.name = "%s: %s" % (self.name, four_vector)
5254

5355
def set_four_vector(self, four_vector):
5456
if not all(0 <= p <= 1 for p in four_vector):

axelrod/tests/integration/test_matches.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,20 @@ def test_outcome_repeats_stochastic(self, strategies, turns, seed):
4343

4444
self.assertEqual(results[0], results[1])
4545
self.assertEqual(results[1], results[2])
46+
47+
def test_matches_with_det_player_for_stochastic_classes(self):
48+
"""A test based on a bug found in the cache.
49+
50+
See: https://github.com/Axelrod-Python/Axelrod/issues/779"""
51+
p1 = axelrod.MemoryOnePlayer((0, 0, 0, 0))
52+
p2 = axelrod.MemoryOnePlayer((1, 0, 1, 0))
53+
p3 = axelrod.MemoryOnePlayer((1, 1, 1, 0))
54+
55+
m = axelrod.Match((p1, p2), turns=3)
56+
self.assertEqual(m.play(), [('C', 'C'), ('D', 'C'), ('D', 'D')])
57+
58+
m = axelrod.Match((p2, p3), turns=3)
59+
self.assertEqual(m.play(), [('C', 'C'), ('C', 'C'), ('C', 'C')])
60+
61+
m = axelrod.Match((p1, p3), turns=3)
62+
self.assertEqual(m.play(), [('C', 'C'), ('D', 'C'), ('D', 'C')])

axelrod/tests/unit/test_deterministic_cache.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,11 @@ class TestDeterministicCache(unittest.TestCase):
1010

1111
@classmethod
1212
def setUpClass(cls):
13-
cls.test_key = (TitForTat, Defector, 3)
13+
cls.test_key = (TitForTat(), Defector(), 3)
1414
cls.test_value = [('C', 'D'), ('D', 'D'), ('D', 'D')]
1515
cls.test_save_file = 'test_cache_save.txt'
1616
cls.test_load_file = 'test_cache_load.txt'
17-
if sys.version_info[0] == 2:
18-
# Python 2.x
19-
cls.test_pickle = b"""(dp0\n(caxelrod.strategies.titfortat\nTitForTat\np1\ncaxelrod.strategies.defector\nDefector\np2\nI3\ntp3\n(lp4\n(S'C'\np5\nS'D'\np6\ntp7\na(g6\ng6\ntp8\na(g6\ng6\ntp9\nas."""
20-
else:
21-
# Python 3.x
22-
cls.test_pickle = b'\x80\x03}q\x00caxelrod.strategies.titfortat\nTitForTat\nq\x01caxelrod.strategies.defector\nDefector\nq\x02K\x03\x87q\x03]q\x04(X\x01\x00\x00\x00Cq\x05X\x01\x00\x00\x00Dq\x06\x86q\x07h\x06h\x06\x86q\x08h\x06h\x06\x86q\tes.'
17+
cls.test_pickle = b'\x80\x03}q\x00X\x0b\x00\x00\x00Tit For Tatq\x01X\x08\x00\x00\x00Defectorq\x02K\x03\x87q\x03]q\x04(X\x01\x00\x00\x00Cq\x05X\x01\x00\x00\x00Dq\x06\x86q\x07h\x06h\x06\x86q\x08h\x06h\x06\x86q\tes.'
2318
with open(cls.test_load_file, 'wb') as f:
2419
f.write(cls.test_pickle)
2520

@@ -102,3 +97,10 @@ def test_load_error_for_inccorect_format(self):
10297
with self.assertRaises(ValueError):
10398
cache = DeterministicCache()
10499
cache.load(filename)
100+
101+
def test_del_item(self):
102+
cache = DeterministicCache()
103+
cache[self.test_key] = self.test_value
104+
self.assertTrue(self.test_key in cache)
105+
del cache[self.test_key]
106+
self.assertFalse(self.test_key in cache)

axelrod/tests/unit/test_match.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def test_init(self, turns, game):
2626
turns
2727
)
2828
self.assertEqual(
29-
match._cache_key, (axelrod.Cooperator, axelrod.Cooperator, turns))
29+
match._cache_key, (p1, p2, turns))
3030
self.assertEqual(match.turns, turns)
3131
self.assertEqual(match._cache, {})
3232
self.assertEqual(match.noise, 0)
@@ -96,11 +96,11 @@ def test_play(self):
9696
expected_result = [(C, D), (C, D), (C, D)]
9797
self.assertEqual(match.play(), expected_result)
9898
self.assertEqual(
99-
cache[(axelrod.Cooperator, axelrod.Defector, 3)], expected_result)
99+
cache[(axelrod.Cooperator(), axelrod.Defector(), 3)], expected_result)
100100

101101
# a deliberately incorrect result so we can tell it came from the cache
102102
expected_result = [(C, C), (D, D), (D, C)]
103-
cache[(axelrod.Cooperator, axelrod.Defector, 3)] = expected_result
103+
cache[(axelrod.Cooperator(), axelrod.Defector(), 3)] = expected_result
104104
match = axelrod.Match(players, 3, deterministic_cache=cache)
105105
self.assertEqual(match.play(), expected_result)
106106

axelrod/tests/unit/test_memoryone.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,27 @@
99

1010
C, D = axelrod.Actions.C, axelrod.Actions.D
1111

12+
class TestGenericPlayerOne(unittest.TestCase):
13+
"""
14+
A class to test the naming and classification of generic memory one players
15+
"""
16+
p1 = axelrod.MemoryOnePlayer((0, 0, 0, 0))
17+
p2 = axelrod.MemoryOnePlayer((1, 0, 1, 0))
18+
p3 = axelrod.MemoryOnePlayer((1, 0.5, 1, 0.5))
19+
20+
def test_name(self):
21+
self.assertEqual(self.p1.name,
22+
"Generic Memory One Player: (0, 0, 0, 0)")
23+
self.assertEqual(self.p2.name,
24+
"Generic Memory One Player: (1, 0, 1, 0)")
25+
self.assertEqual(self.p3.name,
26+
"Generic Memory One Player: (1, 0.5, 1, 0.5)")
27+
28+
def test_stochastic_classification(self):
29+
self.assertFalse(self.p1.classifier['stochastic'])
30+
self.assertFalse(self.p2.classifier['stochastic'])
31+
self.assertTrue(self.p3.classifier['stochastic'])
32+
1233

1334
class TestWinStayLoseShift(TestPlayer):
1435

docs/tutorials/advanced/using_the_cache.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ Let us rerun the above match but using the cache::
4040
We can take a look at the cache::
4141

4242
>>> cache # doctest: +ELLIPSIS
43-
{(<class 'axelrod.strategies.gobymajority.GoByMajority'>, <class 'axelrod.strategies.alternator.Alternator'>, 200): [('C', 'C'), ..., ('C', 'D')]}
43+
{('Soft Go By Majority', 'Alternator', 200): [('C', 'C'), ..., ('C', 'D')]}
4444
>>> len(cache)
4545
1
4646

47-
This maps a triplet of 2 player classes and the match length to the resulting
47+
This maps a triplet of 2 player names and the match length to the resulting
4848
interactions. We can rerun the code and compare the timing::
4949

5050
>>> def run_match_with_cache():

0 commit comments

Comments
 (0)