I'm developing a card game in OOP just for practice, and I have some odd behavior in what Ive written so far. . .When I use the clear method to empty the hands, what happens is the individual hands show they are empty, but when I look at hands variable (that shows both hands), they are NOT empty. My question is why, lol? I'll place the code below and results below. . .Thanks for any help in advance.
import random
class a:
my_hand = []
other_hand = []
hands = [my_hand, other_hand]
def h(self):
for rounds in range(5):
for hand in a.hands:
hand.append(rounds)
def shuffle(self):
random.shuffle(a.my_hand)
random.shuffle(a.other_hand)
def clear(self):
a.my_hand = []
a.other_hand = []
x = a()
x.h()
x.shuffle()
x.hands
[[1, 0, 4, 2, 3], [3, 4, 2, 0, 1]]
x.clear()
[[1, 0, 4, 2, 3], [3, 4, 2, 0, 1]]
x.my_hand
[]
x.other_hand
[]
x.hands
[[1, 0, 4, 2, 3], [3, 4, 2, 0, 1]]
-
Part of the code at the end was cut off. It showed x.other_hand was [], then x.hands was [[1, 0, 4, 2, 3], [3, 4, 2, 0, 1]]. SorryChris Kavanagh– Chris Kavanagh2013年11月09日 06:34:40 +00:00Commented Nov 9, 2013 at 6:34
3 Answers 3
In your a class the my_hand, other_hand and hands variables are all class attributes (i.e. static attributes), not instance attributes.
Also in your clear method you re-assign my_hand and other_hand, but hands still references the old lists, hence its content doesn't change. If you are using python3 then you should use the clear() method of the lists: my_hand.clear() and other_hand.clear(), then hands will reference two empty lists.
On python2 you should do del my_hand[:].
If you want to assign a instance attribute you have to do self.attribute = value.
In order to do so the assignment should be placed inside the __init__ method which is the constructor of the class.
As it currently stands your code, if you create two instances of a then they will share the hands, which is probably something you do not want.
The correct code would be:
import random
class ClassesUseCamelCase(object):
# if you are using python2 do *not* forget to inherit object.
def __init__(self):
self.my_hand = []
self.other_hand = []
self.hands = [self.my_hand, self.other_hand]
def init_hands(self):
for round in range(5):
for hand in self.hands:
hand.append(round)
# or simpler:
# for hand in self.hands:
# hand.extend(range(5))
def shuffle(self):
random.shuffle(self.my_hand)
random.shuffle(self.other_hand)
def clear(self):
self.my_hand.clear() # or del self.my_hand[:] in python2
self.other_hand.clear()
Sample output using iPython:
In [2]: inst = ClassesUseCamelCase()
In [3]: inst.init_hands()
In [4]: inst.shuffle()
In [5]: inst.hands
Out[5]: [[3, 2, 4, 0, 1], [3, 1, 4, 0, 2]]
In [6]: inst.clear()
In [7]: inst.hands
Out[7]: [[], []]
Note that it works with more than one instance:
In [9]: inst.init_hands()
In [10]: other = ClassesUseCamelCase()
In [11]: other.init_hands()
In [12]: inst.hands, other.hands
Out[12]: ([[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]])
In [13]: inst.shuffle(); other.shuffle()
...:
In [14]: inst.hands
Out[14]: [[0, 1, 3, 2, 4], [1, 4, 0, 3, 2]]
In [15]: other.hands
Out[15]: [[1, 4, 2, 0, 3], [1, 4, 3, 2, 0]]
your code would have shared the hands between the two instances.
I'm putting here a bit more explanation on what's happening to avoid writing too many comments.
First of all, in your code the my_hand and other_hand are class attributes. Why does this matter? It matters because class attributes are shared between instances:
In [1]: class MyClass(object):
...: my_hand = []
...: other_hand = []
...: hands = [my_hand, other_hand]
...:
In [2]: instance_one = MyClass()
In [3]: instance_two = MyClass()
In [4]: instance_one.my_hand.append(1)
In [5]: instance_two.my_hand # ops!
Out[5]: [1]
Calling the clear modifies the class attributes and hence all the instances using them. This, usually, is something you do not want.
Tthe instances do not have any other instance attribute which means that all instances are, in fact, equal; they all act the same and provide the same data. The only difference is their identity. If you are not going to use the identity then having a class and multiple instances is kind of useless because you could have simply used a single object.
If you want the instances to have their own data, independent from the data of other instances, then you have to use instance attributes.
Regarding the del statement. As I've already said in the comments del has two different uses. The full syntax of the statement is:
del sequence, of, del_targets
Where the "sequence, of, del_targets" is a comma separated list of what I'll call del_targets. Depending on the del_target the behaviour changes.
If it is an identifer than del removes that reference from the scope, decrementing the reference count of the object that was referred to by the identifier.
For example:
a = b = []
# Now that empty list has two references: a and b
del b # now it has only one reference: a
print(b) # This raises a NameError because b doesn't exist anymore
del a # Now the empty list doesn't have references
If an object doesn't have references it is destroyed, so after the del a above the empty list will be destroyed by the interpreter.
The target can also be a "subscript", i.e. an expression like name[key-or-index] or name[a:slice] (or name[start:stop:step]).
The slices syntax (the one with colon) is used to specify a range of indexes:
In [17]: numbers = list(range(10)) # [0, 1, ..., 9]
In [18]: numbers[::2], numbers[1::2], numbers[2:7:3]
Out[18]: ([0, 2, 4, 6, 8], [1, 3, 5, 7, 9], [2, 5])
When using the del statement with such an expression python calls the __delitem__ method of the object, passing in the index or slice. This means that:
del numbers[:]
Means: delete all the elements of the list numbers that correspond to an index in the slice :. Since the slice : means "all indexes in the sequence" the result is emptying the list. Note that it does not remove the reference from the list. It only acts on its elements.
You can obtain the same effect using:
numbers[:] = []
This tells python to replace the sequence of elements corresponding to the slice : with the elements in []. Since : means "all elements" and [] is empty, the effect is removing all elements from the list.
This syntax calls list.__setitem__ instead of list.__delitem__ under the hood, but the result is the same.
However you can also do: numbers[:] = [2] and then all the elements of numbers will be removed and the 2 will be inserted resulting in the list [2].
Both work, however I prefer the del syntax because it makes explicit your intent.
When you read:
del something[:]
You know that the statement is going to delete something. Then you see the subscript [:] and you understand that it will remove the elements from something and not the reference something. However when you see:
something[:] = []
First you think, okay this is an assignment. Then you see the [:] and you understand that you are doing to "overwrite" the contents of the list. Then you look at the right hand side and see [], so we are going to overwrite the elements with an empty list... finally you understand that the statement will simply remove all the elements from the list.
9 Comments
del self.my_hand[:] note the colon inside the square brackets. del self.my_hand[] is a SyntaxError. And: no, it would not delete the list itself. It will destroy all its elements. The syntax: del something[slice-or-index] where slice-or-index is a single integer or a slice (i.e. the syntax with colon) removes the corresponding elements from the list. For example a = [1,2,3]; del a[1] results in a == [1, 3] and afterwards del a[:] results in a == []. And a is always the same list object.del something only removes a reference to an object, the object may still exist if it has other references. For example a = 1; b = a; del a the del a only reduces the reference count of 1, it does not destroy anything. Last remark: doing del something[slice-or-index] is equivalent to writing something.__delitem__(slice-or-index), in the same manner as something[key] is equivalent to something.__getitem__(key) and something[key] = value is something.__setitem__(key, value).You are creating new empty lists, but hands still references the old ones.
You could simply make sure that hands references the new empty lists.
def clear(self):
a.my_hand = []
a.other_hand = []
a.hands = [a.my_hand, a.other_hand]
or you could modify the lists themselves, by removing all elements.
def clear(self):
del a.my_hand[:]
del a.other_hand[:]
FYI, all of your functions are defined as member methods, but your variables are always static. You may want to reference self instead of a, or make your methods static (with @staticmethod).
1 Comment
As an exercise in OOP your code is strange... you are using class members in instance methods. The class instance in methods is named self, not a. Change your code to
def clear(self):
self.hand = []
self.other_hand = []
and do the same for other parts of the code.
The problem you are facing is however another: when reassigning a.hand and a.other_hand you are creating two new empty arrays, thus a.hands keeps pointing to the old ones created in h.
Changing your code to
a.hand[:] = []
a.other_hand[:] = []
or to the more idiomatic
del a.hand[:]
del a.other_hand[:]
will solve that problem.
In Python the [:] syntax means "all elements of" and allows also slicing (e.g. a.hand[1:-1] is the list of the elemets in a.hand except first and last).
3 Comments
lists have a clear() method that is probably more readable then the del + slicing.