Zeiss Ikon
Zeiss Ikon

Reputation: 481

Yet another "X object has no attribute Y" error

I'm three weeks into learning Python, via "Learn Python the Hard Way" -- since I'm not new to programming, I've been able to progress pretty rapidly through the first half of the book, until I started to get into the OOP portion with classes and objects. Now I'm having a lot of trouble; though I think I've understood the ideas behind these object concepts, I've clearly got something obscurely wrong with my code (I'm using Python 2.7.6, which appears to be part of gcc 4.8.2, in Kubuntu 14.04, kept up to date).

I'm working on Exercise 43, trying to create an adventure game starting from the author's skeleton class definitions. I did pretty well with the first game design (using Python the way I'd have used Basic for the same task, years ago), but I've been about ten console hours trying to beat the latest error in the OOP game; I've read dozens of searched solutions (here and elsewhere) without finding anything that precisely applies. I've pared the code down as much as possible, and I'm still seeing the same error (which I'll paste after the code -- warning, this is still almost 100 lines):

# Python the Hard Way -- Exercise 43: Basic Object-Oriented Analysis and Design

# received as skeleton code, try to make it into a playable game
# my comment: Much harder than designing from scratch!  Author's
# design method (or that appropriate for OOP) differs greatly from
# what I'm used to.

from sys import exit

class UserEntry (object):

  def __init__(self):
    pass

  def get_input (self):

    # initialize variable for trimmed command list
    short_list = []

    # accept input, break at spaces, and reverse for parsing
    command = raw_input ('> ')
    command_list = command.split (' ')
    command_list.reverse ()

    # parse command here
    for i in reversed (xrange (len(command_list))):
      if ((command_list [i] in a_game.act.keys()) or 
          (command_list [i] in a_game.obj.keys())):
        short_list.append (command_list.pop())
      else:
        command_list.pop()
    # return parsed_command
    if len(short_list) == 1 and short_list[0] in a_game.act.keys():
      short_list.append (' ')
    return short_list

class Scene (object):

  def enter(self):
    pass

class Engine (object):

  def __init__(self, scene_map):
    self.scene_map = scene_map
    self.act = {
      'inventory'   :self.inventory,
      'look'        :self.look,
        }

    self.obj = {
      'blaster' :'',
      'corridor':'',
      'gothon'  :'',
      }

  def inventory(self):
    pass

  def look (self):
    pass
  def opening_scene(self):
    # introduce the "plot"
    print "Game intro",

  def play(self):

    entry = UserEntry()
    self.opening_scene()
    a_map.this_scene.enter()

class CentralCorridor(Scene):

  def enter(self):
    print "Central Corridor"

class Map(object):

  def __init__(self, start_scene):
    scenes = {
    'central corridor': CentralCorridor,
      }

    this_scene = scenes[start_scene]()
    print this_scene

a_map = Map('central corridor')
a_game = Engine(a_map)
a_game.play()

When I try to run this, I get the following:

$ python ex43bug.py
<__main__.CentralCorridor object at 0x7f13383c8c10>
Game intro
Traceback (most recent call last):
  File "ex43bug.py", line 89, in <module>
    a_game.play()
  File "ex43bug.py", line 70, in play
    a_map.this_scene.enter()
AttributeError: 'Map' object has no attribute 'this_scene'

Clearly, something is preventing this_scene from being visible to other classes/methods; I just don't get what it is. I don't have indention problems (that I can see), I don't have circular imports (in fact, I'm importing only a single module, for the exit command). The first print line is generated by print this_scene within instance a_map; I should get Game intro and then Central corridor as, first, Engine.opening_scene and then CentralCorridor.enter execute -- but I never get to the latter, despite apparently successfully instantiating CentralCorridor.

I'm baffled. Why isn't a_map.this_scene visible anywhere but within Map.__init__?

Upvotes: 0

Views: 1911

Answers (1)

Martijn Pieters
Martijn Pieters

Reputation: 1123560

this_scene is only ever a local name in Map.__init__:

def __init__(self, start_scene):
    scenes = {
        'central corridor': CentralCorridor,
    }

    this_scene = scenes[start_scene]()
    print this_scene

You need to assign it to an attribute on self:

def __init__(self, start_scene):
    scenes = {
        'central corridor': CentralCorridor,
    }

    self.this_scene = scenes[start_scene]()

Upvotes: 4

Related Questions