On 4 Aug 2010, at 23:28 , Alex Hall wrote:

> On 8/4/10, Evert Rol <evert....@gmail.com> wrote:
>>>>> Further to my questions about overriding builtin methods earlier, how
>>>>> would I make a class able to be accessed and changed using index
>>>>> notation? For example, take the following:
>>>>> deck=CardPile(52) #creates a new deck of cards
>>>>> print(len(deck)) #prints 52, thanks to my __len__ function
>>>>> for c in deck: print c #also works thanks to __iter__
>>>>> print(deck[4]) #fails with a list index out of range error
>>>>> How would I get the last one working? I tried __getattr__(self, i),
>>>>> but it did not work. I want to be able to get an arbitrary item from
>>>>> the "pile" of cards (which can be a deck, a hand, whatever), and/or
>>>>> set an element. A "pile" is just a list of Card objects, so I would
>>>>> only need to use sequence indexing, not mapping functions.
>>>>> 
>>>> 
>>>> Implement __getitem__(self, key) (See
>>>> http://docs.python.org/reference/datamodel.html#emulating-container-types
>>>> )
>>>> 
>>>> If you want to support slicing (access like deck[0:10]), you'll need to
>>>> handle getting a slice object as the key in addition to accepting an
>>>> integer
>>>> key.
>>>> 
>>>> If a pile is really "just" a list of cards, you may want to look into
>>>> inheriting from list instead of re-implementing all of the functionality
>>>> on
>>>> your own.
>>> I tried this first, by typing
>>> class Pile(list):
>>> Doing this does not seem to work, though, since creating a pile of
>>> size 52 results in a list of size 0, unless I include the __len__
>>> function. I thought putting (list) in my class definition would
>>> automatically give me the functions of a list as well as anything I
>>> wanted to implement, but that does not seem to be the case.
>> 
>> That depends how you create the Pile of 52 cards: list(52) also doesn't
>> generate 52 (random) items.
>> If you override __init__ to accept an integer that generates the cards for
>> you, this should work.
> Here is my init, not half as pretty as yours, but it should work.
> Maybe this will explain the problem.
> 
> def __init__(self, size, cards=None, fill=False):
>  #creates a pile of cards. If "fill"==true, it will auto-fill the
> pile starting from the Ace of Clubs up through the King of Spades,
> stopping if it exceeds the size arg.
>  #if the cards arg is not null, it will populate the pile with the
> cards in the list.
>  self=[]

You declare self to be a list here (while somewhere before the def __init__, 
you'll have class Pile(list), meaning every self will be a Pile object. While 
self is an arbitrarily chosen name, it is assigned by the interpreter when 
calling the methods (I hope I'm saying that correctly). So when __init__(self, 
...) is called, self is a Pile object, then you turn self into a list object, 
loosing its meaning. Don't reassign self (but you can do 'print self[:size]'. 
Then again, you probably don't need that inside your class.


Try using your __init__, then at the end of the __init__ method, print 
type(self). That should be something like <type list>. Now do the same with the 
init below that uses super. You should get something like <class Pile>.


>  if fill: #auto-fill, useful to generate a new, unshuffled deck
>   for i in range(1, 14):
>    for j in range(1, 5):
>     self.append(Card(i, j))
>    #end for
>   #end for
>   self=self[:size] #keep only the amount specified
>  elif cards is not None: #fill the pile with the cards
>   for c in cards:
>    self.append(c)
>   #end for
>  #end if
> #end def __init__
> 
> 
>> Something like:
>> 
>> class Pile(list):
>>    def __init__(self, *args, **kwargs):
>>        if len(args) == 1 and isinstance(args[0], (int, long)):
>>            args = ([Card(i) for i in xrange(args[0])],)
>>        super(Pile, self).__init__(*args, **kwargs)
> Why call super here, if it is already my own __init__?


It's just Good Practice to always call super, propagating any remaining 
arguments to the base class __init__ (hence the *args & **kwargs).
Google for "Python super" and read a few of those hits. At first, it may make 
little sense, because there's a lot that can be said about it, but after a 
while, it'll become clearer.


>> allows things like Pile(52), Pile([card1, card2, card3]), Pile(12)[0:3] etc.
> Again, my init is not nearly so fancy, and I will have to look hard at
> what you did to understand it, but they are the same in terms of class
> structure/inheriting list attributes as far as I can see, except the
> call to the super.__init__ method.

If you're confused about the first line, I can image, but it's just a fancy way 
to find out if you initialize the Pile with a list of cards, or with a single 
integer argument.
The *args & **kwargs, again, Google around a bit. In essence, these just 
capture extra argument (positional arguments, then keyword arguments). 
def __init__(self, ncards=0, *args, **kwargs) could even be better, but I'm not 
sure if that works correctly.

But yes, other than the self assignment in your code, the structures are 
essentially the same.


  Evert

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

Reply via email to