Re: [Tutor] Methods defined in my class are affecting all the objects at runtime.

2011-10-29 Thread Brian Stovall
Thanks for all the wonderful help, everyone!

On 10/29/11, Peter Otten <__pete...@web.de> wrote:
> Brian Stovall wrote:
>
>> Hello world!
>>
>> I obviously don't understand something important and basic, but I am
>> having trouble figuring it out myself... I am running python v3.2.2 on
>> a Win XP machine.
>>
>> My code:
>>
>> import card_model
>> import random
>>
>> class Pile:
>> """An Object reperesenting a list of 'Card' Objects: could be a
>> hand, discard pile, tableau
>> or anything really. Has a display_type that is used to clarify
>> what kind of display to use, and
>> .deal(cards[] , new_pile(Pile Object)) and .shuffle() methods."""
>>
>> DISPLAY_TYPES = ["STACK", "FAN", "ACCORDION", "CASCADE"]
>>
>> def __init__(self, cards = [], display_type = "STACK"):
>
> That is a common pitfall: the default values of functions and methods are
> evaluated only once. Therefore all Pile instances created without an
> explicit cards argument
>
> p = Pile()
>
> end up sharing the same cards list. The idiomatic way to avoid that problem
> is a default value of None:
>
> def __init__(self, cards=None, display_type="STACK"):
> if cards is None:
> cards = [] # no list provided -> make a new one
> self.cards = cards
> self.display_type = display_type
>
> Note that this problem can only occur with "mutable" types (types whose
> internal state can be modified at any time); the default for display_type is
> immutable (its state is set once and for all when the object is created) and
> thus cannot cause that kind of trouble.
>
>> self.cards = cards
>> self.display_type = display_type
>>
>> def __str__(self):
>> return_string = ""
>> for i in self.cards:
>> return_string = return_string + str(i) + "\n"
>
> Have a look at str.join(). Example:
>
> "\n".join(str(card) for card in self.cards)
>
>> return_string = return_string + str(self.display_type)
>> return return_string
>>
>> def shuffle(self):
>> random.shuffle(self.cards)
>>
>> def add(self, card_list):
>> for i in card_list:
>> self.cards.append(i)
>
> Have a look at list.extend().
>
>> def deal(self, number_of_cards, position = 0):
>> """Deletes the number of cards out of the pile, starting from
>> position (default is the top) and returns that list of cards, for
>> communication with other piles' .add methods."""
>>
>> dealt_list = []
>> try:
>> for i in range(number_of_cards):
>> dealt_list.append(self.cards[position])
>> del self.cards[position]
>
> Have a look at list.pop() or slices like
>
> cards[position:position+number_of_cards]
>
>> return(dealt_list)
>> except IndexError:
>> print("Error, out of cards!")
>>
>> return(None)
>>
>> I had been testing it with single objects favorably, but when I
>> instantiate two Pile objects, methods like .add or .shuffle affect all
>> of the Pile objects in memory. At first I thought the objects were all
>> initializing to the same space in memory, but it wasn't true. If you
>> need to see all my modules or my tester code, I will happily post.
>>
>> Thanks for helping a rank beginner!
>>
>> -Brian
>> ___
>> Tutor maillist  -  Tutor@python.org
>> To unsubscribe or change subscription options:
>> http://mail.python.org/mailman/listinfo/tutor
>
>
> ___
> Tutor maillist  -  Tutor@python.org
> To unsubscribe or change subscription options:
> http://mail.python.org/mailman/listinfo/tutor
>
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] Methods defined in my class are affecting all the objects at runtime.

2011-10-29 Thread Steven D'Aprano

Brian Stovall wrote:

Hello world!

I obviously don't understand something important and basic, but I am
having trouble figuring it out myself... I am running python v3.2.2 on
a Win XP machine.

[...]

I had been testing it with single objects favorably, but when I
instantiate two Pile objects, methods like .add or .shuffle affect all
of the Pile objects in memory. At first I thought the objects were all
initializing to the same space in memory, but it wasn't true.


The problem is not that all Pile objects are in the same space in 
memory, but that they all share the same cards.


If you call:

a = Pile()
b = Pile()
c = Pile()

then all three Piles a, b, c share the same list of cards. If instead 
you explicitly call Pile([]) each time then I bet the problem will go 
away. Try it and see.


The problem occurs in your __init__ method, where you use a default 
value of [] for cards:


def __init__(self, cards = [], display_type = "STACK"):
self.cards = cards
self.display_type = display_type

The thing is, in Python the default value is only calculated *once*, 
when the function or method is defined, and then reused each time. You 
can test this for yourself very easily:



import time

def test(t=time.asctime()):
print(t)


Call test(), wait a few seconds, and call it again, and precisely the 
same time will be printed. The default value for t is calculated once, 
then reused.


Normally this is not a problem, but if the default value is a list or 
dict, it means that the method or function remembers state from one call 
to the next:


>>> def test(x, alist=[]):
... alist.append(x)  # Modify the list in place.
... return alist
...
>>> test(2)  # The first time you call the function, the default is []
[2]
>>> test(3)  # But now the default is [2]
[2, 3]
>>> test(5)  # And now the default is [2, 3]
[2, 3, 5]



You can easily fix this problem by making sure each Pile instance gets 
its own brand new empty list of cards, instead of sharing the same one:


def __init__(self, cards=None, display_type="STACK"):
if cards is None:
cards = []
self.cards = cards
self.display_type = display_type



A couple of other minor points:

Your add method can be simplified to this:

def add(self, card_list):
self.cards.extend(card_list)


Also, your __str__ method builds up the string by repeated concatenation:

def __str__(self):
return_string = ""
for i in self.cards:
return_string = return_string + str(i) + "\n"
return_string = return_string + str(self.display_type)
return return_string

This is generally poor practice. Without going into a lot of detail, 
this risks being slow in Python. Very, very, VERY slow. Depending on the 
details of your operating system, exact version of Python, the specific 
strings being used, you may not notice any slowdown, but the risk is 
still there. The recommended way to build up a string out of many 
smaller substrings is like this:


def __str__(self):
# First build a list of all of the substrings.
substrings = []
for card in self.cards:
# Please use a more descriptive name than "i"
substrings.append(str(card))
substrings.append(str(self.display_type))
# Now join all of the substrings in one fast operation.
return '\n'.join(substrings)


Hope this helps,


--
Steven
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] Methods defined in my class are affecting all the objects at runtime.

2011-10-29 Thread Peter Otten
Brian Stovall wrote:

> Hello world!
> 
> I obviously don't understand something important and basic, but I am
> having trouble figuring it out myself... I am running python v3.2.2 on
> a Win XP machine.
> 
> My code:
> 
> import card_model
> import random
> 
> class Pile:
> """An Object reperesenting a list of 'Card' Objects: could be a
> hand, discard pile, tableau
> or anything really. Has a display_type that is used to clarify
> what kind of display to use, and
> .deal(cards[] , new_pile(Pile Object)) and .shuffle() methods."""
> 
> DISPLAY_TYPES = ["STACK", "FAN", "ACCORDION", "CASCADE"]
> 
> def __init__(self, cards = [], display_type = "STACK"):

That is a common pitfall: the default values of functions and methods are 
evaluated only once. Therefore all Pile instances created without an 
explicit cards argument

p = Pile()

end up sharing the same cards list. The idiomatic way to avoid that problem 
is a default value of None:

def __init__(self, cards=None, display_type="STACK"):
if cards is None:
cards = [] # no list provided -> make a new one
self.cards = cards
self.display_type = display_type

Note that this problem can only occur with "mutable" types (types whose 
internal state can be modified at any time); the default for display_type is 
immutable (its state is set once and for all when the object is created) and 
thus cannot cause that kind of trouble.

> self.cards = cards
> self.display_type = display_type
> 
> def __str__(self):
> return_string = ""
> for i in self.cards:
> return_string = return_string + str(i) + "\n"

Have a look at str.join(). Example:

"\n".join(str(card) for card in self.cards)

> return_string = return_string + str(self.display_type)
> return return_string
> 
> def shuffle(self):
> random.shuffle(self.cards)
> 
> def add(self, card_list):
> for i in card_list:
> self.cards.append(i)

Have a look at list.extend().
 
> def deal(self, number_of_cards, position = 0):
> """Deletes the number of cards out of the pile, starting from
> position (default is the top) and returns that list of cards, for
> communication with other piles' .add methods."""
> 
> dealt_list = []
> try:
> for i in range(number_of_cards):
> dealt_list.append(self.cards[position])
> del self.cards[position]

Have a look at list.pop() or slices like 

cards[position:position+number_of_cards]
 
> return(dealt_list)
> except IndexError:
> print("Error, out of cards!")
> 
> return(None)
> 
> I had been testing it with single objects favorably, but when I
> instantiate two Pile objects, methods like .add or .shuffle affect all
> of the Pile objects in memory. At first I thought the objects were all
> initializing to the same space in memory, but it wasn't true. If you
> need to see all my modules or my tester code, I will happily post.
> 
> Thanks for helping a rank beginner!
> 
> -Brian
> ___
> Tutor maillist  -  Tutor@python.org
> To unsubscribe or change subscription options:
> http://mail.python.org/mailman/listinfo/tutor


___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor


[Tutor] Methods defined in my class are affecting all the objects at runtime.

2011-10-29 Thread Brian Stovall
Hello world!

I obviously don't understand something important and basic, but I am
having trouble figuring it out myself... I am running python v3.2.2 on
a Win XP machine.

My code:

import card_model
import random

class Pile:
"""An Object reperesenting a list of 'Card' Objects: could be a
hand, discard pile, tableau
or anything really. Has a display_type that is used to clarify
what kind of display to use, and
.deal(cards[] , new_pile(Pile Object)) and .shuffle() methods."""

DISPLAY_TYPES = ["STACK", "FAN", "ACCORDION", "CASCADE"]

def __init__(self, cards = [], display_type = "STACK"):
self.cards = cards
self.display_type = display_type

def __str__(self):
return_string = ""
for i in self.cards:
return_string = return_string + str(i) + "\n"
return_string = return_string + str(self.display_type)
return return_string

def shuffle(self):
random.shuffle(self.cards)

def add(self, card_list):
for i in card_list:
self.cards.append(i)

def deal(self, number_of_cards, position = 0):
"""Deletes the number of cards out of the pile, starting from
position (default is the top) and returns that list of cards, for
communication with other piles' .add methods."""

dealt_list = []
try:
for i in range(number_of_cards):
dealt_list.append(self.cards[position])
del self.cards[position]

return(dealt_list)
except IndexError:
print("Error, out of cards!")

return(None)

I had been testing it with single objects favorably, but when I
instantiate two Pile objects, methods like .add or .shuffle affect all
of the Pile objects in memory. At first I thought the objects were all
initializing to the same space in memory, but it wasn't true. If you
need to see all my modules or my tester code, I will happily post.

Thanks for helping a rank beginner!

-Brian
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor