diff --git a/axelrod/mock_player.py b/axelrod/mock_player.py index 0dbadee92..f639f3eb6 100644 --- a/axelrod/mock_player.py +++ b/axelrod/mock_player.py @@ -14,7 +14,7 @@ class MockPlayer(Player): def __init__(self, player, move): # Need to retain history for opponents that examine opponents history # Do a deep copy just to be safe - axelrod.Player.__init__(self) + Player.__init__(self) self.history = copy.deepcopy(player.history) self.cooperations = player.cooperations self.defections = player.defections diff --git a/axelrod/strategies/_strategies.py b/axelrod/strategies/_strategies.py index 6a2fe797b..390a38cec 100644 --- a/axelrod/strategies/_strategies.py +++ b/axelrod/strategies/_strategies.py @@ -8,7 +8,7 @@ from .backstabber import BackStabber, DoubleCrosser from .calculator import Calculator from .cooperator import Cooperator, TrickyCooperator -from .cycler import AntiCycler, CyclerCCD, CyclerCCCD, CyclerCCCCCD +from .cycler import AntiCycler, Cycler, CyclerCCD, CyclerCCCD, CyclerCCCCCD from .darwin import Darwin from .defector import Defector, TrickyDefector from .forgiver import Forgiver, ForgivingTitForTat diff --git a/axelrod/strategies/averagecopier.py b/axelrod/strategies/averagecopier.py index a77072d1e..944e240e0 100644 --- a/axelrod/strategies/averagecopier.py +++ b/axelrod/strategies/averagecopier.py @@ -1,9 +1,7 @@ - from axelrod import Player, random_choice, Actions import random - class AverageCopier(Player): """ The player will cooperate with probability p if the opponent's cooperation ratio is p. diff --git a/axelrod/strategies/axelrod_tournaments.py b/axelrod/strategies/axelrod_tournaments.py index 59752d480..c0d6d7bee 100644 --- a/axelrod/strategies/axelrod_tournaments.py +++ b/axelrod/strategies/axelrod_tournaments.py @@ -10,9 +10,6 @@ C, D = Actions.C, Actions.D - -## First Tournament - class Davis(Player): """A player starts by cooperating for 10 rounds then plays Grudger, defecting if at any point the opponent has defected.""" diff --git a/axelrod/strategies/backstabber.py b/axelrod/strategies/backstabber.py index 3094c9178..60de076f9 100644 --- a/axelrod/strategies/backstabber.py +++ b/axelrod/strategies/backstabber.py @@ -1,7 +1,10 @@ from axelrod import Player, Actions +from axelrod.strategy_transformers import FinalTransformer + C, D = Actions.C, Actions.D +@FinalTransformer([D, D]) # End with three defections class BackStabber(Player): """ Forgives the first 3 defections but on the fourth @@ -20,8 +23,6 @@ class BackStabber(Player): def strategy(self, opponent): if not opponent.history: return C - if len(opponent.history) > (self.tournament_attributes['length'] - 3): - return D if opponent.defections > 3: return D return C diff --git a/axelrod/strategy_transformers.py b/axelrod/strategy_transformers.py new file mode 100644 index 000000000..6a9191518 --- /dev/null +++ b/axelrod/strategy_transformers.py @@ -0,0 +1,230 @@ + +""" +Strategy Transformers -- class decorators that transform the behavior of any +strategy. + +See the various Meta strategies for another type of transformation. +""" + +import random +from types import FunctionType + +from .actions import Actions, flip_action +from .random_ import random_choice + +C, D = Actions.C, Actions.D + +# Note: After a transformation is applied, +# the player's history is overwritten with the modified history +# just like in the noisy tournament case +# This can lead to unexpected behavior, such as when +# FlipTransform is applied to Alternator + +def generic_strategy_wrapper(player, opponent, proposed_action, *args, **kwargs): + """ + Strategy wrapper functions should be of the following form. + + Parameters + ---------- + player: Player object or subclass (self) + opponent: Player object or subclass + proposed_action: an axelrod.Action, C or D + The proposed action by the wrapped strategy + proposed_action = Player.strategy(...) + args, kwargs: + Any additional arguments that you need. + + Returns + ------- + action: an axelrod.Action, C or D + + """ + + # This example just passes through the proposed_action + return proposed_action + +def StrategyTransformerFactory(strategy_wrapper, wrapper_args=(), + wrapper_kwargs={}, name_prefix=None): + """Modify an existing strategy dynamically by wrapping the strategy + method with the argument `strategy_wrapper`. + + Parameters + ---------- + strategy_wrapper: function + A function of the form `strategy_wrapper(player, opponent, proposed_action, *args, **kwargs)` + Can also use a class that implements + def __call__(self, player, opponent, action) + wrapper_args: tuple + Any arguments to pass to the wrapper + wrapper_kwargs: dict + Any keyword arguments to pass to the wrapper + name_prefix: string, "Transformed " + A string to prepend to the strategy and class name + """ + + # Create a function that applies a wrapper function to the strategy method + # of a given class + def decorate(PlayerClass, name_prefix=name_prefix): + """ + Parameters + ---------- + PlayerClass: A subclass of axelrod.Player, e.g. Cooperator + The Player Class to modify + name_prefix: str + A string to prepend to the Player and Class name + + Returns + ------- + new_class, class object + A class object that can create instances of the modified PlayerClass + """ + + # Define the new strategy method, wrapping the existing method + # with `strategy_wrapper` + def strategy(self, opponent): + # Is the original strategy method a static method? + if isinstance(PlayerClass.__dict__["strategy"], staticmethod): + proposed_action = PlayerClass.strategy(opponent) + else: + proposed_action = PlayerClass.strategy(self, opponent) + # Apply the wrapper + return strategy_wrapper(self, opponent, proposed_action, + *wrapper_args, **wrapper_kwargs) + + # Define a new class and wrap the strategy method + # Modify the PlayerClass name + new_class_name = PlayerClass.__name__ + name = PlayerClass.name + if name_prefix: + # Modify the Player name (class variable inherited from Player) + new_class_name = name_prefix + PlayerClass.__name__ + # Modify the Player name (class variable inherited from Player) + name = name_prefix + ' ' + PlayerClass.name + # Dynamically create the new class + new_class = type(new_class_name, (PlayerClass,), + {"name": name, "strategy": strategy}) + return new_class + return decorate + +def flip_wrapper(player, opponent, action): + """Applies flip_action at the class level.""" + return flip_action(action) + +FlipTransformer = StrategyTransformerFactory(flip_wrapper, name_prefix="Flipped") + +def noisy_wrapper(player, opponent, action, noise=0.05): + """Applies flip_action at the class level.""" + r = random.random() + if r < noise: + return flip_action(action) + return action + +def NoisyTransformer(noise, name_prefix="Noisy"): + """Creates a function that takes an axelrod.Player class as an argument + and alters the play of the Player in the following way. The player's + intended action is flipped with probability noise.""" + + return StrategyTransformerFactory(noisy_wrapper, + wrapper_args=(noise,), + name_prefix=name_prefix) + +def forgiver_wrapper(player, opponent, action, p): + """If a strategy wants to defect, flip to cooperate with the given + probability.""" + if action == D: + return random_choice(p) + return C + +def ForgiverTransformer(p, name_prefix="Forgiving"): + """Creates a function that takes an axelrod.Player class as an argument + and alters the play of the Player in the following way. The player's + defections are flipped with probability p.""" + + return StrategyTransformerFactory(forgiver_wrapper, wrapper_args=(p,), + name_prefix=name_prefix) + +def initial_sequence(player, opponent, action, initial_seq): + """Play the moves in `seq` first (must be a list), ignoring the strategy's + moves until the list is exhausted.""" + + index = len(player.history) + if index < len(initial_seq): + return initial_seq[index] + return action + +## Defection initially three times +def InitialTransformer(seq=None): + """Creates a function that takes an axelrod.Player class as an argument + and alters the play of the Player in the following way. The player starts + with the actions in the argument seq and then proceeds to play normally.""" + + if not seq: + seq = [D] * 3 + transformer = StrategyTransformerFactory(initial_sequence, + wrapper_args=(seq,)) + return transformer + +def final_sequence(player, opponent, action, seq): + """Play the moves in `seq` first, ignoring the strategy's moves until the + list is exhausted.""" + + length = player.tournament_attributes["length"] + + if length < 0: # default is -1 + return action + + index = length - len(player.history) + if index <= len(seq): + return seq[-index] + return action + +def FinalTransformer(seq=None): + """Creates a function that takes an axelrod.Player class as an argument + and alters the play of the Player in the following way. If the tournament + length is known, the play ends with the actions in the argument seq. + Otherwise the player's actions are unaltered. """ + + if not seq: + seq = [D] * 3 + transformer = StrategyTransformerFactory(final_sequence, + wrapper_args=(seq,)) + return transformer + +# Strategy wrapper as a class example +class RetaliationWrapper(object): + """Enforces the TFT rule that the opponent pay back a defection with a + cooperation for the player to stop defecting.""" + def __init__(self): + self.is_retaliating = False + + def __call__(self, player, opponent, action): + if len(player.history) == 0: + return action + if opponent.history[-1] == D: + self.is_retaliating = True + if self.is_retaliating: + if opponent.history[-1] == C: + self.is_retaliating = False + return C + return D + return action + +def RetailiateUntilApologyTransformer(name_prefix="RUA"): + """Creates a function that takes an axelrod.Player class as an argument + and alters the play of the Player in the following way. If the opponent + defects, the player will retaliate with defections until the opponent + cooperates. Otherwise the player's actions are unaltered.""" + + strategy_wrapper = RetaliationWrapper() + return StrategyTransformerFactory(strategy_wrapper, name_prefix=name_prefix) + +def history_track_wrapper(player, opponent, action): + """Wrapper to track a player's history in a variable `._recorded_history`.""" + try: + player._recorded_history.append(action) + except AttributeError: + player._recorded_history = [action] + return action + +TrackHistoryTransformer = StrategyTransformerFactory(history_track_wrapper, + name_prefix="HistoryTracking") diff --git a/axelrod/tests/unit/test_backstabber.py b/axelrod/tests/unit/test_backstabber.py index c285da3d5..0c854d9b6 100644 --- a/axelrod/tests/unit/test_backstabber.py +++ b/axelrod/tests/unit/test_backstabber.py @@ -32,8 +32,13 @@ def test_strategy(self): tournament_length=200) # Defects on rounds 199, and 200 no matter what + self.responses_test([C] * 197 , [C] * 197, [C, D, D], + tournament_length=200) self.responses_test([C] * 198 , [C] * 198, [D, D, D], tournament_length=200) + # But only if the tournament is known + self.responses_test([C] * 198 , [C] * 198, [C, C, C], + tournament_length=-1) class TestDoubleCrosser(TestPlayer): diff --git a/axelrod/tests/unit/test_strategy_transformers.py b/axelrod/tests/unit/test_strategy_transformers.py new file mode 100644 index 000000000..5a184e9d8 --- /dev/null +++ b/axelrod/tests/unit/test_strategy_transformers.py @@ -0,0 +1,191 @@ +import random +import unittest + +import axelrod +from axelrod import simulate_play +from axelrod.strategy_transformers import * + +C, D = axelrod.Actions.C, axelrod.Actions.D + + +class TestTransformers(unittest.TestCase): + + def test_naming(self): + """Tests that the player and class names are properly modified.""" + cls = FlipTransformer(axelrod.Cooperator) + p1 = cls() + self.assertEqual(cls.__name__, "FlippedCooperator") + self.assertEqual(p1.name, "Flipped Cooperator") + + def test_cloning(self): + """Tests that Player.clone preserves the application of transformations. + """ + p1 = axelrod.Cooperator() + p2 = FlipTransformer(axelrod.Cooperator)() # Defector + p3 = p2.clone() + self.assertEqual(simulate_play(p1, p3), (C, D)) + self.assertEqual(simulate_play(p1, p3), (C, D)) + + def test_generic(self): + """Test that the generic wrapper does nothing.""" + transformer = StrategyTransformerFactory(generic_strategy_wrapper) + Cooperator2 = transformer(axelrod.Cooperator) + p1 = Cooperator2() + p2 = axelrod.Cooperator() + self.assertEqual(simulate_play(p1, p2), (C, C)) + self.assertEqual(simulate_play(p1, p2), (C, C)) + + def test_flip_transformer(self): + """Tests that FlipTransformer(Cooperator) == Defector.""" + p1 = axelrod.Cooperator() + p2 = FlipTransformer(axelrod.Cooperator)() # Defector + self.assertEqual(simulate_play(p1, p2), (C, D)) + self.assertEqual(simulate_play(p1, p2), (C, D)) + self.assertEqual(simulate_play(p1, p2), (C, D)) + + def test_noisy_transformer(self): + """Tests that the noisy transformed does flip some moves.""" + random.seed(5) + # Cooperator to Defector + p1 = axelrod.Cooperator() + p2 = NoisyTransformer(0.5)(axelrod.Cooperator)() + for _ in range(10): + p1.play(p2) + self.assertEqual(p2.history, [C, C, C, C, C, C, D, D, C, C]) + + def test_forgiving(self): + """Tests that the forgiving transformer flips some defections.""" + random.seed(10) + p1 = ForgiverTransformer(0.5)(axelrod.Alternator)() + p2 = axelrod.Defector() + for _ in range(10): + p1.play(p2) + self.assertEqual(p1.history, [C, D, C, C, D, C, C, D, C, D]) + + def test_cycler(self): + """A test that demonstrates the difference in outcomes if + FlipTransformer is applied to Alternator and CyclerCD.""" + # Difference between Alternator and CyclerCD + p1 = axelrod.Cycler(cycle="CD") + p2 = FlipTransformer(axelrod.Cycler)(cycle="CD") + for _ in range(5): + p1.play(p2) + self.assertEqual(p1.history, [C, D, C, D, C]) + self.assertEqual(p2.history, [D, C, D, C, D]) + + p1 = axelrod.Alternator() + p2 = FlipTransformer(axelrod.Alternator)() + for _ in range(5): + p1.play(p2) + self.assertEqual(p1.history, [C, D, C, D, C]) + self.assertEqual(p2.history, [D, D, D, D, D]) + + def test_initial_transformer(self): + """Tests the InitialTransformer.""" + p1 = axelrod.Cooperator() + p2 = InitialTransformer([D, D])(axelrod.Cooperator)() + for _ in range(5): + p1.play(p2) + self.assertEqual(p2.history, [D, D, C, C, C]) + + p1 = axelrod.Cooperator() + p2 = InitialTransformer([D, D, C, D])(axelrod.Cooperator)() + for _ in range(5): + p1.play(p2) + self.assertEqual(p2.history, [D, D, C, D, C]) + + def test_final_transformer(self): + """Tests the FinalTransformer when tournament length is known.""" + # Final play transformer + p1 = axelrod.Cooperator() + p2 = FinalTransformer([D, D, D])(axelrod.Cooperator)() + p2.tournament_attributes["length"] = 6 + for _ in range(6): + p1.play(p2) + self.assertEqual(p2.history, [C, C, C, D, D, D]) + + def test_final_transformer2(self): + """Tests the FinalTransformer when tournament length is not known.""" + p1 = axelrod.Cooperator() + p2 = FinalTransformer()(axelrod.Cooperator)() + for _ in range(6): + p1.play(p2) + self.assertEqual(p2.history, [C, C, C, C, C, C]) + + def test_history_track(self): + """Tests the history tracking transformer.""" + p1 = axelrod.Cooperator() + p2 = TrackHistoryTransformer(axelrod.Random)() + for _ in range(6): + p1.play(p2) + self.assertEqual(p2.history, p2._recorded_history) + + def test_composition(self): + """Tests that transformations can be chained or composed.""" + cls1 = InitialTransformer()(axelrod.Cooperator) + cls2 = FinalTransformer()(cls1) + p1 = cls2() + p2 = axelrod.Cooperator() + p1.tournament_attributes["length"] = 8 + for _ in range(8): + p1.play(p2) + self.assertEqual(p1.history, [D, D, D, C, C, D, D, D]) + + cls1 = FinalTransformer()(InitialTransformer()(axelrod.Cooperator)) + p1 = cls1() + p2 = axelrod.Cooperator() + p1.tournament_attributes["length"] = 8 + for _ in range(8): + p1.play(p2) + self.assertEqual(p1.history, [D, D, D, C, C, D, D, D]) + + def test_retailiation(self): + """Tests the RetailiateUntilApologyTransformer.""" + RUA = RetailiateUntilApologyTransformer() + TFT = RUA(axelrod.Cooperator) + p1 = TFT() + p2 = axelrod.Cooperator() + p1.play(p2) + p1.play(p2) + self.assertEqual(p1.history, [C, C]) + + p1 = TFT() + p2 = axelrod.Defector() + p1.play(p2) + p1.play(p2) + self.assertEqual(p1.history, [C, D]) + + random.seed(12) + p1 = TFT() + p2 = axelrod.Random() + for _ in range(5): + p1.play(p2) + self.assertEqual(p1.history, [C, C, D, D, C]) + + + +## Test that RUA(Cooperator) is the same as TitForTat +## reusing the TFT tests. Since TFT is completely specified by its tests, +## this is actually a proof that they are equal! +## However because classifier is a class variable until after instantiation +## this alters Cooperator's class variable, and causes its test to fail +## So for now this is commented out. + +#RUA = RetailiateUntilApologyTransformer() +#TFT = RUA(axelrod.Cooperator) +#TFT.name = "Tit For Tat" +#TFT.classifier["memory_depth"] = 1 + +#class TestRUAisTFT(test_titfortat.TestTitForTat): + ## This runs the 7 TFT tests when unittest is invoked + #player = TFT + +## Test that FlipTransformer(Defector) == Cooperator +#Cooperator2 = FlipTransformer(axelrod.Defector) +#Cooperator2.name = "Cooperator" +#Cooperator2.classifier["memory_depth"] = 0 + + +#class TestFlipDefector(test_cooperator.TestCooperator): + ## This runs the 7 TFT tests when unittest is invoked + #player = Cooperator2 diff --git a/docs/tutorials/advanced/index.rst b/docs/tutorials/advanced/index.rst index ddfd52de6..127501408 100644 --- a/docs/tutorials/advanced/index.rst +++ b/docs/tutorials/advanced/index.rst @@ -8,3 +8,5 @@ Contents: .. toctree:: :maxdepth: 2 + + strategy_transformers.rst \ No newline at end of file diff --git a/docs/tutorials/advanced/strategy_transformers.rst b/docs/tutorials/advanced/strategy_transformers.rst new file mode 100644 index 000000000..0105d7aa3 --- /dev/null +++ b/docs/tutorials/advanced/strategy_transformers.rst @@ -0,0 +1,195 @@ +.. _strategy_transformers: + +Strategy Transformers +===================== + +What is a Strategy Transfomer? +------------------------------ + +A strategy transformer is a function that modifies an existing strategy. For +example, :code:`FlipTransformer` takes a strategy and flips the actions from +C to D and D to C:: + + >>> import axelrod + >>> from axelrod.strategy_transformers import FlipTransformer + >>> FlippedCooperator = FlipTransformer(axelrod.Cooperator) + >>> player = FlippedCooperator() + >>> opponent = axelrod.Cooperator() + >>> player.strategy(opponent) + 'D' + >>> opponent.strategy(player) + 'C' + +Our player was switched from a :code:`Cooperator` to a :code:`Defector` when +we applied the transformer. The transformer also changed the name of the +class and player:: + + >>> player.name + 'Flipped Cooperator' + >>> FlippedCooperator.name + 'Flipped Cooperator' + +This behavor can be supressed by setting the :code:`name_prefix` argument:: + + FlipTransformer = StrategyTransformerFactory(flip_wrapper, name_prefix="") + +Note carefully that the transformer returns a class, not an instance of a class. +This means that you need to use the Transformed class as you would normally to +create a new instance:: + + >>> import axelrod + >>> from axelrod.strategy_transformers import NoisyTransformer + >>> player = NoisyTransformer(0.5)(axelrod.Cooperator)() + +rather than :code:`NoisyTransformer(0.5)(axelrod.Cooperator())` or just :code:`NoisyTransformer(0.5)(axelrod.Cooperator)`. + +You can also chain together multiple transformers:: + + cls1 = FinalTransformer([D,D])(InitialTransformer([D,D])(axelrod.Cooperator)) + p1 = cls1() + +This defines a strategy that cooperates except on the first two and last two rounds. + + +Included Transformers +--------------------- + +The library includes the following transformers: + +* :code:`FlipTransformer`: Flips all actions:: + + >>> import axelrod + >>> from axelrod.strategy_transformers import FlipTransformer + >>> FlippedCooperator = FlipTransformer(axelrod.Cooperator) + >>> player = FlippedCooperator() + +* :code:`NoisyTransformer(noise)`: Flips actions with probability :code:`noise`:: + + >>> import axelrod + >>> from axelrod.strategy_transformers import NoisyTransformer + >>> player = NoisyTransformer(0.5)(axelrod.Cooperator)() + +* :code:`ForgiverTransformer(p)`: Flips defections with probability :code:`p`:: + + >>> import axelrod + >>> from axelrod.strategy_transformers import ForgiverTransformer + >>> player = ForgiverTransformer(0.1)(axelrod.Defector)() + +* :code:`InitialTransformer(seq=None)`: First plays the moves in the sequence :code:`seq`, then plays as usual. For example, to obtain a defector that cooperates on the first two rounds:: + + >>> import axelrod + >>> from axelrod.strategy_transformers import InitialTransformer + >>> player = InitialTransformer([C, C])(axelrod.Defector)() + +* :code:`FinalTransformer(seq=None)`: Ends the tournament with the moves in the sequence :code:`seq`, if the tournament_length is known. For example, to obtain a cooperator that defects on the last two rounds:: + + >>> import axelrod + >>> from axelrod.strategy_transformers import FinalTransformer + >>> player = FinalTransformer([D, D])(axelrod.Cooperator)() + +* :code:`RetailiateUntilApologyTransformer()`: adds TitForTat-style retaliation:: + + >>> import axelrod + >>> from axelrod.strategy_transformers import RetailiateUntilApologyTransformer + >>> RUA = RetailiateUntilApologyTransformer() + >>> TFT = RUA(axelrod.Cooperator) + +* :code:`TrackHistoryTransformer`: Tracks History internally in the :code:`Player` instance in a variable :code:`_recorded_history`. This allows a player to e.g. detect noise.:: + + >>> import axelrod + >>> from axelrod.strategy_transformers import TrackHistoryTransformer + >>> player = TrackHistoryTransformer(axelrod.Random)() + +Usage as Class Decorators +------------------------- + +Transformers can also be used to decorate existing strategies. For example, +the strategy :code:`BackStabber` defects on the last two rounds. We can encode this +behavior with a transformer as a class decorator:: + + @FinalTransformer([D, D]) # End with three defections + class BackStabber(Player): + """ + Forgives the first 3 defections but on the fourth + will defect forever. Defects on the last 2 rounds unconditionally. + """ + + name = 'BackStabber' + classifier = { + 'memory_depth': float('inf'), + 'stochastic': False, + 'inspects_source': False, + 'manipulates_source': False, + 'manipulates_state': False + } + + def strategy(self, opponent): + if not opponent.history: + return C + if opponent.defections > 3: + return D + return C + + +Writing New Transformers +------------------------ + +To make a new transformer, you need to define a strategy wrapping function with +the following signature:: + + def strategy_wrapper(player, opponent, proposed_action, *args, **kwargs): + """ + Strategy wrapper functions should be of the following form. + + Parameters + ---------- + player: Player object or subclass (self) + opponent: Player object or subclass + proposed_action: an axelrod.Action, C or D + The proposed action by the wrapped strategy + proposed_action = Player.strategy(...) + args, kwargs: + Any additional arguments that you need. + + Returns + ------- + action: an axelrod.Action, C or D + + """ + + # This example just passes through the proposed_action + return proposed_action + +The proposed action will be the outcome of:: + self.strategy(player) +in the underlying class (the one that is transformed). The strategy_wrapper still +has full access to the player and the opponent objects and can have arguments. + +To make a transformer from the :code:`strategy_wrapper` function, use +:code:`StrategyTransformerFactory`, which has signature:: + + def StrategyTransformerFactory(strategy_wrapper, wrapper_args=(), + wrapper_kwargs={}, name_prefix=""): + """Modify an existing strategy dynamically by wrapping the strategy + method with the argument `strategy_wrapper`. + + Parameters + ---------- + strategy_wrapper: function + A function of the form `strategy_wrapper(player, opponent, proposed_action, *args, **kwargs)` + Can also use a class that implements + def __call__(self, player, opponent, action) + wrapper_args: tuple + Any arguments to pass to the wrapper + wrapper_kwargs: dict + Any keyword arguments to pass to the wrapper + name_prefix: string, "Transformed " + A string to prepend to the strategy and class name + """ + +So we use :code:`StrategyTransformerFactory` with :code:`strategy_wrapper`:: + + TransformedClass = StrategyTransformerFactory(generic_strategy_wrapper) + Cooperator2 = TransformedClass(axelrod.Cooperator) + +For more examples, see :code:`axelrod/strategy_transformers.py`.