9
9
import copy
10
10
import inspect
11
11
import random
12
+ from typing import Any
12
13
from numpy .random import choice
13
14
from .action import Action
14
15
from .random_ import random_choice
16
+ from .player import defaultdict , Player
15
17
from importlib import import_module
16
18
17
19
@@ -59,9 +61,10 @@ def __init__(self, *args, **kwargs):
59
61
self .name_prefix = name_prefix
60
62
61
63
def __reduce__ (self ):
64
+ factory_args = (strategy_wrapper , name_prefix , reclassifier )
62
65
return (
63
66
DecoratorReBuilder (),
64
- (strategy_wrapper , name_prefix , reclassifier ,
67
+ (factory_args ,
65
68
self .args , self .kwargs , self .name_prefix )
66
69
)
67
70
@@ -96,8 +99,8 @@ def __call__(self, PlayerClass):
96
99
# with `strategy_wrapper`
97
100
def strategy (self , opponent ):
98
101
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 .
101
104
proposed_action = C
102
105
else :
103
106
if is_strategy_static (PlayerClass ):
@@ -147,23 +150,23 @@ def __repr__(self):
147
150
def reduce_for_decorated_class (self_ ):
148
151
class_module = import_module (self_ .__module__ )
149
152
import_name = self_ .__class__ .__name__
150
- if import_name in dir (class_module ):
153
+
154
+ if player_can_be_pickled (self_ ):
151
155
return self_ .__class__ , (), self_ .__dict__
152
156
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
+ )
167
170
168
171
# Define a new class and wrap the strategy method
169
172
# Dynamically create the new class
@@ -185,11 +188,22 @@ def reduce_for_decorated_class(self_):
185
188
return Decorator
186
189
187
190
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.
189
194
"""
195
+ class_module = import_module (player .__module__ )
196
+ import_name = player .__class__ .__name__
197
+ if not hasattr (class_module , import_name ):
198
+ return False
190
199
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.
193
207
"""
194
208
for klass in player_class .mro ():
195
209
method = inspect .getattr_static (klass , 'strategy' , default = None )
@@ -198,23 +212,26 @@ def is_strategy_static(player_class):
198
212
199
213
200
214
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 )
209
223
kwargs ['name_prefix' ] = instance_name_prefix
210
224
return decorator_class (* args , ** kwargs )
211
225
212
226
213
227
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 :
216
234
217
- def __call__ (self , decorators , import_name , module_name ):
218
235
module_ = import_module (module_name )
219
236
import_class = getattr (module_ , import_name )
220
237
@@ -277,7 +294,7 @@ def flip_wrapper(player, opponent, action):
277
294
flip_wrapper , name_prefix = "Flipped" )
278
295
279
296
280
- def dual_wrapper (player , opponent , proposed_action ) :
297
+ def dual_wrapper (player , opponent : Player , proposed_action : Action ) -> Action :
281
298
"""Wraps the players strategy function to produce the Dual.
282
299
283
300
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):
298
315
action: an axelrod.Action, C or D
299
316
"""
300
317
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 )
308
319
309
320
if is_strategy_static (player .original_class ):
310
321
action = player .original_class .strategy (opponent )
311
322
else :
312
323
action = player .original_class .strategy (player , opponent )
313
324
314
- player .history = temp_history [:]
315
- switch_cooperations_and_defections (player )
325
+ flip_play_attributes (player )
316
326
317
- player .original_player_history .append (action )
318
327
return action .flip ()
319
328
320
329
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`."""
322
351
temp = player .cooperations
323
352
player .cooperations = player .defections
324
353
player .defections = temp
325
354
326
355
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
+
327
365
DualTransformer = StrategyTransformerFactory (dual_wrapper , name_prefix = "Dual" )
328
366
329
367
0 commit comments