Skip to content

Commit 26f27e1

Browse files
eric-s-sdrvinceknight
authored andcommitted
mostly fixed. branch switch
1 parent c8ae6cf commit 26f27e1

File tree

3 files changed

+277
-109
lines changed

3 files changed

+277
-109
lines changed

axelrod/strategy_transformers.py

+82-44
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
import copy
1010
import inspect
1111
import random
12+
from typing import Any
1213
from numpy.random import choice
1314
from .action import Action
1415
from .random_ import random_choice
16+
from .player import defaultdict, Player
1517
from importlib import import_module
1618

1719

@@ -59,9 +61,10 @@ def __init__(self, *args, **kwargs):
5961
self.name_prefix = name_prefix
6062

6163
def __reduce__(self):
64+
factory_args = (strategy_wrapper, name_prefix, reclassifier)
6265
return (
6366
DecoratorReBuilder(),
64-
(strategy_wrapper, name_prefix, reclassifier,
67+
(factory_args,
6568
self.args, self.kwargs, self.name_prefix)
6669
)
6770

@@ -96,8 +99,8 @@ def __call__(self, PlayerClass):
9699
# with `strategy_wrapper`
97100
def strategy(self, opponent):
98101
if strategy_wrapper == dual_wrapper:
99-
# Dummy Action for dual_wrapper.
100-
# This is to avoid calling the strategy twice.
102+
# `dual_wrapper` is a special case that must not call the
103+
# strategy here. It is called inside the wrapper, instead.
101104
proposed_action = C
102105
else:
103106
if is_strategy_static(PlayerClass):
@@ -147,23 +150,23 @@ def __repr__(self):
147150
def reduce_for_decorated_class(self_):
148151
class_module = import_module(self_.__module__)
149152
import_name = self_.__class__.__name__
150-
if import_name in dir(class_module):
153+
154+
if player_can_be_pickled(self_):
151155
return self_.__class__, (), self_.__dict__
152156

153-
else:
154-
decorators = []
155-
for klass in self_.__class__.mro():
156-
if hasattr(klass, 'decorator'):
157-
decorators.insert(0, klass.decorator)
158-
else:
159-
import_name = klass.__name__
160-
break
161-
162-
return (
163-
StrategyReBuilder(),
164-
(decorators, import_name, self_.__module__),
165-
self_.__dict__
166-
)
157+
decorators = []
158+
for klass in self_.__class__.mro():
159+
import_name = klass.__name__
160+
if hasattr(klass, 'decorator'):
161+
decorators.insert(0, klass.decorator)
162+
if hasattr(class_module, import_name):
163+
break
164+
165+
return (
166+
StrategyReBuilder(),
167+
(decorators, import_name, self_.__module__),
168+
self_.__dict__
169+
)
167170

168171
# Define a new class and wrap the strategy method
169172
# Dynamically create the new class
@@ -185,11 +188,22 @@ def reduce_for_decorated_class(self_):
185188
return Decorator
186189

187190

188-
def is_strategy_static(player_class):
191+
def player_can_be_pickled(player: Player) -> bool:
192+
"""
193+
Returns True if pickle.dump(player) does not raise pickle.PicklingError.
189194
"""
195+
class_module = import_module(player.__module__)
196+
import_name = player.__class__.__name__
197+
if not hasattr(class_module, import_name):
198+
return False
190199

191-
:param player_class: Any class that inherits from axelrod.Player
192-
:return: bool
200+
to_test = getattr(class_module, import_name)
201+
return to_test == player.__class__
202+
203+
204+
def is_strategy_static(player_class) -> bool:
205+
"""
206+
Returns True if `player_class.strategy` is a `staticmethod`, else False.
193207
"""
194208
for klass in player_class.mro():
195209
method = inspect.getattr_static(klass, 'strategy', default=None)
@@ -198,23 +212,26 @@ def is_strategy_static(player_class):
198212

199213

200214
class DecoratorReBuilder(object):
201-
def __init__(self):
202-
pass
203-
204-
def __call__(self, strategy_wrapper, name_prefix, reclassifier,
205-
args, kwargs, instance_name_prefix):
206-
decorator_class = StrategyTransformerFactory(
207-
strategy_wrapper, name_prefix, reclassifier
208-
)
215+
"""
216+
An object to build an anonymous Decorator obj from a set of pickle-able
217+
parameters.
218+
"""
219+
def __call__(self, factory_args: tuple, args: tuple, kwargs: dict,
220+
instance_name_prefix: str) -> Any:
221+
222+
decorator_class = StrategyTransformerFactory(*factory_args)
209223
kwargs['name_prefix'] = instance_name_prefix
210224
return decorator_class(*args, **kwargs)
211225

212226

213227
class StrategyReBuilder(object):
214-
def __init__(self):
215-
pass
228+
"""
229+
An object to build a new instance of a player from an old instance
230+
that could not normally be pickled.
231+
"""
232+
def __call__(self, decorators: list, import_name: str,
233+
module_name: str) -> Player:
216234

217-
def __call__(self, decorators, import_name, module_name):
218235
module_ = import_module(module_name)
219236
import_class = getattr(module_, import_name)
220237

@@ -277,7 +294,7 @@ def flip_wrapper(player, opponent, action):
277294
flip_wrapper, name_prefix="Flipped")
278295

279296

280-
def dual_wrapper(player, opponent, proposed_action):
297+
def dual_wrapper(player, opponent: Player, proposed_action: Action) -> Action:
281298
"""Wraps the players strategy function to produce the Dual.
282299
283300
The Dual of a strategy will return the exact opposite set of moves to the
@@ -298,32 +315,53 @@ def dual_wrapper(player, opponent, proposed_action):
298315
action: an axelrod.Action, C or D
299316
"""
300317

301-
if not player.history:
302-
player.original_player_history = []
303-
304-
temp_history = player.history[:]
305-
player.history = player.original_player_history[:]
306-
307-
switch_cooperations_and_defections(player)
318+
flip_play_attributes(player)
308319

309320
if is_strategy_static(player.original_class):
310321
action = player.original_class.strategy(opponent)
311322
else:
312323
action = player.original_class.strategy(player, opponent)
313324

314-
player.history = temp_history[:]
315-
switch_cooperations_and_defections(player)
325+
flip_play_attributes(player)
316326

317-
player.original_player_history.append(action)
318327
return action.flip()
319328

320329

321-
def switch_cooperations_and_defections(player):
330+
def flip_play_attributes(player: Player) -> None:
331+
"""
332+
Flips all the attributes created by `player.play`:
333+
- `player.history`,
334+
- `player.cooperations`,
335+
- `player.defectsions`,
336+
- `player.state_distribution`,
337+
"""
338+
flip_history(player)
339+
switch_cooperations_and_defections(player)
340+
flip_state_distribution(player)
341+
342+
343+
def flip_history(player: Player) -> None:
344+
"""Flips all the actions in `player.history`."""
345+
new_history = [action.flip() for action in player.history]
346+
player.history = new_history
347+
348+
349+
def switch_cooperations_and_defections(player: Player) -> None:
350+
"""Exchanges `player.cooperations` and `player.defections`."""
322351
temp = player.cooperations
323352
player.cooperations = player.defections
324353
player.defections = temp
325354

326355

356+
def flip_state_distribution(player: Player) -> None:
357+
"""Flips all the player's actions in `player.state_distribution`."""
358+
new_distribution = defaultdict(int)
359+
for key, val in player.state_distribution.items():
360+
new_key = (key[0].flip(), key[1])
361+
new_distribution[new_key] = val
362+
player.state_distribution = new_distribution
363+
364+
327365
DualTransformer = StrategyTransformerFactory(dual_wrapper, name_prefix="Dual")
328366

329367

axelrod/tests/unit/test_pickling.py

+21-45
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,14 @@ class RetaliateUntilApology(axl.Cooperator):
126126
class TrackHistory(axl.Cooperator):
127127
pass
128128

129+
@st.IdentityTransformer()
130+
class Identity(axl.Cooperator):
131+
pass
132+
129133
transformed_no_prefix = [Apology, DeadlockBreaking, Flip, Final, Forgiver,
130134
Grudge, Initial, JossAnn, Mixed, Nice, Noisy,
131-
Retaliation, RetaliateUntilApology, TrackHistory, Dual]
135+
Retaliation, RetaliateUntilApology, TrackHistory, Dual,
136+
Identity]
132137

133138
transformer_instances = [
134139
st.ApologyTransformer([D], [C]),
@@ -145,12 +150,17 @@ class TrackHistory(axl.Cooperator):
145150
st.NoisyTransformer(0.2),
146151
st.RetaliationTransformer(3),
147152
st.RetaliateUntilApologyTransformer(),
148-
st.TrackHistoryTransformer()
153+
st.TrackHistoryTransformer(),
154+
st.IdentityTransformer()
149155
]
150156

151157

152158
class TestPickle(unittest.TestCase):
153159

160+
def assert_orignal_equals_pickled(self, player, turns=10):
161+
self.assert_original_plays_same_as_pickled(player, turns)
162+
self.assert_mutated_instance_same_as_pickled(player)
163+
154164
def assert_original_plays_same_as_pickled(self, player, turns=10):
155165
copy = pickle.loads(pickle.dumps(player))
156166
opponent_1 = axl.CyclerCCCDCD()
@@ -166,6 +176,7 @@ def assert_original_plays_same_as_pickled(self, player, turns=10):
166176
self.assertEqual(result_1, result_2)
167177

168178
def assert_mutated_instance_same_as_pickled(self, player):
179+
player.reset()
169180
turns = 5
170181
opponent = axl.Alternator()
171182
for _ in range(turns):
@@ -175,11 +186,11 @@ def assert_mutated_instance_same_as_pickled(self, player):
175186

176187
def test_parameterized_player(self):
177188
player = axl.Cycler('DDCCDD')
178-
self.assert_mutated_instance_same_as_pickled(player)
189+
self.assert_orignal_equals_pickled(player)
179190

180191
def test_sequence_player(self):
181192
player = axl.ThueMorse()
182-
self.assert_mutated_instance_same_as_pickled(player)
193+
self.assert_orignal_equals_pickled(player)
183194

184195
def test_final_transformer_called(self):
185196
player = axl.Alexei()
@@ -196,20 +207,17 @@ def test_nice_transformer_class(self):
196207

197208
def test_pickling_all_strategies(self):
198209
for s in axl.strategies:
199-
player = s()
200-
player.play(axl.Cooperator())
201-
reconstituted = pickle.loads(pickle.dumps(player))
202-
self.assertEqual(reconstituted, player)
210+
self.assert_orignal_equals_pickled(s())
203211

204212
def test_pickling_all_transformers_as_decorated_classes(self):
205213
for s in transformed_no_prefix:
206214
player = s()
207-
self.assert_mutated_instance_same_as_pickled(player)
215+
self.assert_orignal_equals_pickled(player)
208216

209217
def test_pickling_all_transformers_as_instance_called_on_a_class(self):
210218
for transformer in transformer_instances:
211219
player = transformer(axl.Cooperator)()
212-
self.assert_mutated_instance_same_as_pickled(player)
220+
self.assert_orignal_equals_pickled(player)
213221

214222
def test_created_on_the_spot_multiple_transformers(self):
215223
klass = st.FlipTransformer()(axl.Cooperator)
@@ -218,6 +226,7 @@ def test_created_on_the_spot_multiple_transformers(self):
218226
copy = pickle.loads(pickle.dumps(player))
219227

220228
self.assertEqual(player, copy)
229+
self.assert_mutated_instance_same_as_pickled(player)
221230

222231
def test_class_and_instance_name_different_single_flip(self):
223232
player = SingleFlip()
@@ -307,42 +316,9 @@ def test_with_various_name_prefixes(self):
307316
'FliptasticCooperator')
308317
self.assert_mutated_instance_same_as_pickled(new_prefix)
309318

310-
def test_limitations__pickling_a_stupid_case(self):
319+
def test_dynamic_class_no_name_prefix(self):
311320
player = st.FlipTransformer(name_prefix=None)(axl.Cooperator)()
312321

313322
self.assertEqual(player.__class__.__name__, 'Cooperator')
323+
self.assert_mutated_instance_same_as_pickled(player)
314324

315-
self.assertRaises(pickle.PicklingError, pickle.dumps, player)
316-
317-
def test_regression_test_dual_transformer_with_lookerup(self):
318-
self.assert_dual_wrapper_correct(axl.LookerUp)
319-
self.assert_dual_wrapper_correct(axl.EvolvedLookerUp2_2_2)
320-
321-
def test_regression_test_dual_jossann(self):
322-
klass = st.JossAnnTransformer((0.2, 0.3))(axl.Alternator)
323-
self.assert_dual_wrapper_correct(klass)
324-
325-
klass = st.JossAnnTransformer((0.5, 0.4))(axl.EvolvedLookerUp2_2_2)
326-
self.assert_dual_wrapper_correct(klass)
327-
328-
def test_dual_transformer_with_fsm_and_sequence_players(self):
329-
330-
for s in axl.strategies:
331-
self.assert_dual_wrapper_correct(s)
332-
333-
def assert_dual_wrapper_correct(self, player_class):
334-
p1 = player_class()
335-
p2 = st.DualTransformer()(player_class)()
336-
p3 = axl.CyclerCCD() # Cycles 'CCD'
337-
338-
axl.seed(0)
339-
for _ in range(10):
340-
p1.play(p3)
341-
342-
p3.reset()
343-
344-
axl.seed(0)
345-
for _ in range(10):
346-
p2.play(p3)
347-
348-
self.assertEqual(p1.history, [x.flip() for x in p2.history])

0 commit comments

Comments
 (0)