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