[Tutor] What's in a name?

Alan Gauld alan.gauld at btinternet.com
Fri Jan 3 12:41:06 CET 2014


On 03/01/14 08:37, Keith Winston wrote:
> Below you will find the full current version of my Chutes or Snakes &
> Ladders game. I tried to reorganize it into a more OOP structure,

This is off topic but a couple of points about the OOP stuff...

Its normal style to name classes with a capital letter so it
would be Games not games.

It's also better to name classes as a singular. They create
a single object not many so it would really be Game not Games.

Finally the class name should be specific to what the class
is. You class is not a generic game class its very specifically
a ChutesAndLadders game class.

So a better name is

class ChutesAndLadders:...

Finally, one comment that is more apropos to the current thread.
You started by saying:

"If I'm iterating a variable through a series of list names,
for future processing, I would like to print the name of the
list the variable is set to in a given moment... "

Remember that variables in Python are just names.
So we can rewrite your statement as

"If I'm iterating a name through a series of list names, for future 
processing, I would like to print the name of the list the name is set 
to in a given moment... "

And since the list names are just strings we can write:

"If I'm iterating a name through a list of strings, for future 
processing, I would like to print the name in a given moment...

So the answer to your question is just to print the string.
The real challenge, as we have discovered, is how to access
the list that is named by the string. And the usual way to
map strings to objects is via a dictionary not by using eval().

Which kind of takes us back to where we were...


> class games:
>      """Game class for Chutes & Ladders."""
>
>      def __init__(self):
>          self.reset()
>
>      def reset(self):
>          self.move_count = 0
>          self.num_chutes = 0
>          self.num_ladders = 0
>          self.chutes_list = []
>          self.ladders_list = []

Do you really need the num_xxx variables?
Using len(xxx_list) is more reliable that depending on your
code to maintain the values. It may be that you need them
for a performance optimisation but given that games are
not normally CPU bound that seems unlikely.

Or does num_xxx indicate something different?
Looking briefly at the move()  code it may be they
represent the number of xxx encountered during the game?

>      def move(self):
>          """Single move, with chutes, ladders, & out of bounds.
>          Primary action is to move self.position, but returns a list
>          that consists of either the chute or ladder if landed on, if either
>          """
>
>          roll = random.randint(1,6)
>          tchutes = 0
>          tladders = 0
>          self.move_count += 1
>          self.position += roll
>          if self.position in chutes:
>              tchutes = self.position
>              self.position = chutes[self.position]
>              self.num_chutes += 1
>          elif self.position in ladders:
>              tladders = self.position
>              self.position = ladders[self.position]
>              self.num_ladders += 1
>          elif self.position > 100:  # move doesn't count, have to land
> exactly
>              self.position -= roll
>          return [tchutes, tladders]  # only one will be != 0

I don't understand the tchutes/tladders stuff? They hold the
target position or zero (the return comment is wrong BTW since both
could be zero). Why not just have a single variable called target
or somesuch? Also if you must return two values its probably better to 
use a tuple rather than a list.

>      def play_game(self, step):
>          """Single game"""

Since this is part of a games/Game/ChutesAndLadders class you probably 
don't need the '_game' bit of the name, it doesn't convey any extra 
information. Just a thought...

>          self.position = 0
>          self.reset()

Shouldn't the self.position assignment be part of reset()?

>          while self.position < 100:
>              gamecandl = self.move()  # [chute, ladder] or [0, 0]
>              if gamecandl[0] != 0: # populate chutes list
>                  self.chutes_list.append(gamecandl[0])
>              if gamecandl[1] != 0:  # populate ladders list
>                  self.ladders_list.append(gamecandl[1])

Why don't you do this update of the lists in the move() code.
It's where it logically happens and saves passing back the
list/tuple and then having to test it here.  This just adds
extra work.

>          return [step, self.move_count, self.num_chutes,
> self.num_ladders, self.chutes_list, self.ladders_list]

In OOP you rarely have to return attributes. And since step
is passed in as an argument the caller already knows. So I
don't think you really need to return anything here.

> #    @timer
>      def play_gameset(self, gamecount1):
>          """A set of games, returning associated stats array"""
>
>          return [self.play_game(i) for i in range(gamecount1)]

OK, I see now, you are storing the current state values
after each game. Personally I'd probably create another
method called (get_stats() or similar and have play() return 
success/failure.

Then your line above would be:

return [self.get_stats() for i in range(gamecount) if self.play()]

It keeps the purpose of the methods explicit. play() plays the game, 
get_stats() returns the data.

But there is another more OOP approach.
Create a game instance for each iteration.
Then collect the instances which then hold the data.
No need for all the extra arrays, return values, etc.
It's by using and passing whole *objects* around that
it is *Object Oriented*. We are trying to move away
from knowing about the internal data.

> def candl(gamecount2):
>      """ play a mess of games, return the results array """
>
>      gname = games()

So rather than having a game instance created here create
it in play_gameset which becomes a function  rather than
a method... And if play() returns self...

def play_gameset(count):
     return [ChutesAndLadders().play() for n in range(count)]

If you wanted to create other games later you could even
extend it to take the game class as an argument:

def play_gameset(count, game):
     return [game().play() for n in range(count)]

>      game_array = gname.play_gameset(gamecount2)

game_array then truly is an array of game objects.

>      print_gameset_stats(game_array)
>      print_candl_info(game_array)

And the stats functions calculate their stats by extracting
them from the objects (using our get_stats() method of course! :-)

In general if you create a class then only have a single instance
that's a bad OOP sign. You probably only need a module... (There are 
situations where a singleton object is useful, but in Python its very rare)

HTH
-- 
Alan G
Author of the Learn to Program web site
http://www.alan-g.me.uk/
http://www.flickr.com/photos/alangauldphotos



More information about the Tutor mailing list