-
Notifications
You must be signed in to change notification settings - Fork 282
Implement an eq method for players #975
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| setattr(other, attribute, | ||
| (ele for ele in original_other_value)) | ||
|
|
||
| if not (all(next(generator) == next(other_generator) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could cause unintended behavior if used during a match or tournament.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see what you mean?
I've done this in such a way that the generator gets copied and there are tests that show that using __eq__ does not alter the generator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lines 199 and 215:
187 def test_equality_for_generator(self):
188 # Check generator attribute (a special case)
189 p1 = axelrod.Cooperator()
190 p2 = axelrod.Cooperator()
191 # Check that the generator is still the same
192 p1.generator = (i for i in range(10))
193 p2.generator = (i for i in range(10))
194 self.assertEqual(p1, p2)
195
196 _ = next(p2.generator)
197 self.assertNotEqual(p1, p2)
198
199 # Check that internal generator object has not been changed
200 self.assertEqual(list(p1.generator), list(range(10)))
201 self.assertEqual(list(p2.generator), list(range(1, 10)))
202
203 def test_equality_for_cycle(self):
204 # Check cycle attribute (a special case)
205 p1 = axelrod.Cooperator()
206 p2 = axelrod.Cooperator()
207 # Check that the cycle is still the same
208 p1.cycle = itertools.cycle(range(10))
209 p2.cycle = itertools.cycle(range(10))
210 self.assertEqual(p1, p2)
211
212 _ = next(p2.cycle)
213 self.assertNotEqual(p1, p2)
214
215 # Check that internal generator object has not been changed
216 self.assertEqual(next(p1.cycle), 0)
217 self.assertEqual(next(p2.cycle), 1) There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So when the generator is copied it's state is also reset?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup that's right. What itertools.tee does is essentially fork a generator in to two versions of itself from the state it currently is in.
That is also being tests:
-
Create two
Cooperatorsand give them two cycle generators and tests that they are equal (at this stage their generator are in the same state:205 p1 = axelrod.Cooperator() 206 p2 = axelrod.Cooperator() 207 # Check that the cycle is still the same 208 p1.cycle = itertools.cycle(range(10)) 209 p2.cycle = itertools.cycle(range(10)) 210 self.assertEqual(p1, p2) -
Bump
p2s cycle and now the players are not the same:212 _ = next(p2.cycle) 213 self.assertNotEqual(p1, p2) -
Check what the next values in the cycle are for each player, for
p1it should be0because the player has not iterated through any values and forp2it should be1because it has iterated through the first value:215 # Check that internal generator object has not been changed 216 self.assertEqual(next(p1.cycle), 0) 217 self.assertEqual(next(p2.cycle), 1)
Let me know if you think I could clarify that in the code and/or add other tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI test_equality_on_init is the test that fails in this case.
This should clarify why:
>>> import itertools
>>> import types
>>> cycle = itertools.cycle('ABCD')
>>> new_cycle, backup = itertools.tee(cycle)
>>> type(cycle), type(new_cycle), type(backup)
(itertools.cycle, itertools._tee, itertools._tee)
>>> isinstance(cycle, itertools.cycle), isinstance(cycle, types.GeneratorType)
(True, False)
>>> isinstance(new_cycle, itertools.cycle), isinstance(backup, types.GeneratorType)
(False, False)So the second time around the generator/cycle is not compared in the correct way. I did have a special case for a cycle but found it more elegant to just treat them all the same way. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could add isinstance(value, collections.Iterable) to the first check but that would also catch lists etc which we don't want :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did have a special case for a cycle but found it more elegant to just treat them all the same way.
This might actually be a better way as it ensures we don't change anything at all (even the type of the attributes). I've also realised something else that's not right in this test. Going to tweak and push.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In c5e95d6 I've added a third equality test (theoretically things could break on the second one). I've also added test that ensure the type of a generator/cycle stays the same (which implies I've changed the way I've dealt with the output of itertools.tee). 👍 Let me know what you think :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanations!
| p1 = axelrod.Cooperator() | ||
| p2 = axelrod.Cooperator() | ||
| self.assertEqual(p1, p2) | ||
| p1.__repr__ = lambda :"John Nash" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
spacing
- Add equality method and tests to player. - Add documentation for player equality. - Refactored some other tests to make use of this method - Refactor a match generator test which was failing as a result of the eq method. Previously this was testing equality of instance. Some other minor changes I've made as a result of this: - Change when qlearner makes random choice. I've also removed the reset test. This is now being tested by the parent test class for all strategies. - Fix small bug in contrite. Without this, contrite was not resetting correctly (the recorded history was being maintained in the class attribute). This closes #885 but also closes #940 which was worked on a while ago but which has not seen any progress.
|
I approve. One comment for thought -- it seems possible to get into an infinite loop if player A contains an attribute referencing player B and player B contains a reverse reference. I couldn't think of a legitimate reason to do so -- even with the meta players we don't let them be team members of other meta players -- but maybe worth some thought for the possibility of other cases. |
|
That's a really good point. Let me have a play with a couple of ideas I've got. Will push/comment later today. |
Check equality of players when players have attributes that point at each other. Also included some more tests for basic attribute equality.
|
I've covered this with: eb612f5 Have added a bunch of tests that check we get what we want :) (I also added a couple of other specific tests for some cases I thought were worth checking :)) |
Thanks 👍 :) |
|
@meatballs you're up when you get a chance |
eq method. Previously this was testing equality of instance.
Some other minor changes I've made as a result of this:
test. This is now being tested by the parent test class for all
strategies.
correctly (the recorded history was being maintained in the class
attribute).
This closes #885 but also closes #940 which was worked on a while ago
but which has not seen any progress.