Ex 43 AttributeError: 'NoneType' object has no attribute 'enter'

I’m working on ex43 and admittedly am just writing down the posted code as I don’t understand classes yet.

When I run the program, in most cases I end up with the following error:

Traceback (most recent call last):
      File "ex43.py", line 200, in <module>
        a_game.play()      
File "ex43.py", line 25, in play
        next_scene_name = current_scene.enter()
    AttributeError: 'NoneType' object has no attribute 'enter'`

Each class ends with a return to the scenes dict, which from what I understand based on this Reddit thread should be all that is required to avoid returning None. But it throws an error anyways.

What is it about a_game.play() and next_scene_name = current_scene.enter() that is not talking to the scenes dict?

Code here:

from sys import exit
from random import randint
from textwrap import dedent



class Scene(object):

    def enter(self):
        print("This scene is not yet implemented.")
        print("Subclass it an implement enter().")
        exit(1)


class Engine(object):

    def __init__(self, scene_map):
        self.scene_map = scene_map

    def play(self):
        current_scene = self.scene_map.opening_scene()
        last_scene = self.scene_map.next_scene("finished")

        while current_scene != last_scene:
            next_scene_name = current_scene.enter()
            current_scene = self.scene_map.next_scene(next_scene_name)

        #be sure to print out the last Scene
        current_scene.enter()



class Death(Scene):

    quips = [
        "You died.",
        "Ur ded.",
        "Game error. Your fault.",
        "Don't even bother trying again.",
        "I still don't understand the logic of this program. Bye."
    ]

    def enter(self):
        print(Death.quips[randint(0, len(self.quips)-1)])


class CentralCorridor(Scene):

    def enter(self):
        print(dedent("""
        This is the start of the game.
        Choose a number: 1, 2 or 3.
        """))

        action = input("What's your number?: ")

        if action == "1":
            print(dedent("""
            This choice was unsuccessful.
            Why did you think you're #1?
            Narcissist.
            """))
            return 'death'

        elif action == "2":
            print(dedent("""
                Unfortunately, you chose wrong.
                Now you're going to suffer the consequenses.
                Inconsequential.
                """))
            return 'death'

        elif action == "3":
            print(dedent("""
                You're lucky.
                Three is a solid number.
                You may contiue.
                """))
            return 'laser_weapon_armory'

        else:
            print("DOES NOT COMPUTE!")
            return 'central_corridor'


class LaserWeaponArmory(Scene):

    def enter(self):
        print(dedent("""
            You now have to choose three digits.
            Choose correctly, you move on.
            Incorectly, you fail.
            You have 10 tries.
            """))

        code = f"{randint(1,9)}, {randint(1,9)}, {randint(1,9)}"
        guess = input("Choose now [keypad]: ")

        if guess == code:
            print(dedent("""
                You win!
                We move ahead.
                """))
            return 'the_bridge'

        else:
            print(dedent("""
                You fail.
                Unsurprising.
                """))
            return 'death'




class TheBridge(Scene):

    def enter(self):
        print(dedent("""Get ready. You're going to have to choose a number again.
            Choose one from 1 to 9.
            """))

        action = input("What number?: ")

        if action == "1":
            print(dedent("""
                No sorry, you're dead.
                """))

            return 'death'

        elif action == "5":
            print(dedent("""
                Lucky! You advance!
                """))

            return 'escape_pod'

        else:
            print("DOES NOT COMPUTE!")

            return 'the_bridge'


class EscapePod(Scene):

    def enter(self):
        print(dedent("""
        Now you need to pick a number from 1 to 5.
        """))

        good_pod = randint(1,5)
        guess = input("Go ahead [pod #]: ")

        if int(guess) != good_pod:
            print(dedent("""
            No. That's wrong.
            """))
            return 'death'

        else:
            print("You won!")
            return 'finished'

class Finished(Scene):

    def enter(self):
        print(dedent("""
        Good job.
        """))
        return 'finished'


class Map(object):

    scenes = {
        'central_corridor': CentralCorridor(),
        'laser_weapon_armory': LaserWeaponArmory(),
        'the_bridge': TheBridge(),
        'escape_pod': EscapePod(),
        'death': Death(),
        'finished': Finished()
        }

    def __init__(self, start_scene):
        self.start_scene = start_scene

    def next_scene(self, scene_name):
        val  = Map.scenes.get(scene_name)
        return val

    def opening_scene(self):
        return self.next_scene(self.start_scene)


a_map = Map('central_corridor')

a_game = Engine(a_map)

a_game.play()

Hello @heavywind.

If you put your code in here there will be many eyes in here that can help you to find what causes this error. The reason can be small and simple. But very hard to spot.

Type in before your code:
”[code]”

Paste your code between these lines.

Finish with: ”[/code]”

Thanks, @ulfen69. Edited original post to include code.

1 Like

Hello @heavywind, If you notice on page 206 in the book on your class ‘Death’ you also have the exit(1), which helps you to exit your game once you lose in the game.

class Death(Scene):

quips = [
    "You died.",
    "Ur ded.",
    "Game error. Your fault.",
    "Don't even bother trying again.",
    "I still don't understand the logic of this program. Bye."
]

def enter(self):
    print(Death.quips[randint(0, len(self.quips)-1)])
    exit(1)

on this specific part:

def enter(self):
    print(Death.quips[randint(0, len(self.quips)-1)])
    exit(1)

Also in your LaserWeaponArmory class:

code = f"{randint(1,9)}, {randint(1,9)}, {randint(1,9)}"

you would need to get rid of the commas that separate the random integers like this:

 code = f"{randint(1,9)}{randint(1,9)}{randint(1,9)}"

Hope this help!

When you get this error it’s usually because you forgot a return in a function. So if you do this:

def badfunc():
    print("I'm going to return a value.")
    # notice no return here

x = badfunc()
x.dothing()

The reason this happens is the value you get from calling a function that does not return something is None. When you try to call a function on None you get this error. You fix it by returning:

def badfunc():
   print("I'm going to return.")
   return thing

x = badfunc()
x.dothat()

So, you have a room that has an enter() function that does not return. Just have to find out where that is.

I am curious, why exit(1)? Why not exit(0) or exit(Death)?

For
code = f"{randint(1,9)}{randint(1,9)}{randint(1,9)}"
are the commas removed because then the user would have to enter x, x, x instead of xxx? Or is there another reason?

Thanks for your help, @zedshaw and @Alfredo13!

Hello @heavywind,

This reference below gives you a very good basic answer to your question about exit(1) and exit(0):


exit(0) means a clean exit without any errors/problems

exit(1) means there was some issue/error/problem and that is why the program is exiting.


That is as far as I understand for these two function calls. I wouldn’t worry too much about the details as of right now

for the rest of the code on string formatting, try to run it with the commas and see what happens and then without the commas and also see what happens. Also, see the error messages and try to understand what is trying to tell you, but basically you got the right idea.

I had all these questions as well and right now know the very basic functionality of these as well.

Hope this help!

2 Likes

I am having the same problem, but I check and all my enter() present a return or include a pass. I cannot get past inputing Adventure and going to the Adventure class. I get this error:

File “motato.py”, line 138, in
a_game.play()
File “motato.py”, line 24, in play
next_scene_name = current_scene.enter()
AttributeError: ‘NoneType’ object has no attribute ‘enter’

from sys import exit
from sys import argv
from random import randint
from textwrap import dedent
from os.path import exists
from datetime import datetime, timedelta, date
import time

script, from_file, to_file = argv

class Timer():
    def timer(self):
        pass

class Engine():
    def __init__(self, universe_portal):
        self.universe_portal = universe_portal

    def play(self):
        current_scene = self.universe_portal.opening_scene()
        last_scene = self.universe_portal.next_scene('finished')

        while current_scene != last_scene:
            next_scene_name = current_scene.enter()
            current_scene = self.universe_portal.next_scene(next_scene_name)
        current_scene.enter()

class Scene():
    def enter(self):
        print("SCENE DUMMY")
        exit(1)

class Death(Scene):
    def enter(self):
        pass

class Portal(Scene):

    def enter(self):
        print(dedent("""You are safe at home. What do you want to do?
    [ Check Inventory ] [ Adventure ] [ Brew ]
        """))

        choice = input("> ")

        if 'inventory' or 'Inventory' in choice:
            return 'inventory'

        elif 'adventure' or 'Adventure' in choice:
            return 'adventure'

        elif 'brew' in choice:
            return 'brew_system'

        else:
            print("DOES NOT COMPUTE")
            return 'portal'

class Adventure(Scene):

    def enter(self):
        print(dedent(""""
        Where shall we start our adventure?
        [ Rivers of Plenty ] [ SUGAR HELL ] [ Ginger Forest ]
        """))

        space_travel = input("> ")

        if 'rivers' or 'Rivers' in space_travel:
            return 'rivers_of_plenty'

        elif 'hell' or 'Hell' in space_travel:
            return 'sugar_hell'

        elif 'ginger' or 'Ginger' in space_travel:
            return 'ginger_forest'

        else:
            print("CANNOT COMPUTE")
            return 'portal'


class Inventory(Scene):
    def enter(self):
        pass

class RiversOfPlenty(Scene):
    def enter(self):
        pass
    def WaterGoddess(self):
        pass

class SugarHell(Scene):
    def enter(self):
        pass
    def SweetSatan(self):
        pass

class GingerForest(Scene):
    def enter(self):
        pass
    def GingerElve(self):
        pass

class BrewSys(Scene):
    def enter(self):
        pass

class Finished(Scene):
    def enter(self):
        pass

class UniversePortal():

    scenes = {
        'portal': Portal(),
        'inventory': Inventory(),
        'adventure': Adventure(),
        'brew_system': BrewSys(),
        'sugar_hell': SugarHell(),
        'rivers_of_plenty': RiversOfPlenty(),
        'ginger_forest': GingerForest(),
        'finished': Finished()
    }

    def __init__(self, start_scene):
        self.start_scene = start_scene

    def next_scene(self, scene_name):
        val = UniversePortal.scenes.get(scene_name)
        return val

    def opening_scene(self):
        return self.next_scene(self.start_scene)

a_universe = UniversePortal('portal')
a_game = Engine(a_universe)
a_game.play()

Right before that line do this:

print(">>> SCENE", next_scene_name);

That will then tell you what scene is causing the error.

Thanks Zed. right before which line? I have been struggling with this for over a week

Right before the line where the error occurs, 24 according to the call stack.

A function with a pass statement returns None, too.

1 Like

Typically you’d put prints like that before or after every variable that changes. So, try printing out every variable that you assign something to. It’s a little excessive but it’ll teach you what to log and help you find the bug.

You can also do this:

https://docs.python.org/3/library/pdb.html

1 Like

Amazing, i already found it was “inventory” but I cannot understand why! I finally am moving so thank you for this motivation. I will dive into printing every variable I assign to something. Thank you!!

Oh! I see what’s happening! Python’s easy syntax has tricked you into believing that you could write plain English. :wink:

Your if statements don’t do what you expect. In Portal.enter():

if 'inventory' or 'Inventory' in choice:
    return 'inventory'

The in operator has a higher precedence than or, so the test is evaluated as

('inventory') or ('Inventory' in choice)

and because non-empty strings count as true in tests, the first half always succeeds. So you get sent to the inventory no matter what you choose in the portal scene. Inventory.enter() isn’t implemented, returns None and then it blows up.

What you want is

'inventory' in choice or 'Inventory' in choice

or, another way:

'inventory' in choice.lower()
1 Like

Yep! This is the evil of logic because it will treat the word ‘inventory’ as if it’s “truthy”, so that evaluates to true.

1 Like