[Tutor] access class through indexing?

Evert Rol evert.rol at gmail.com
Wed Aug 4 23:52:57 CEST 2010


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

> On 8/4/10, Evert Rol <evert.rol at 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



More information about the Tutor mailing list