pythontypeerrorturtle-graphicspython-turtle

Python Turtle Module, inheriting from Screen-class, function() argument 'code' must be code, not str


I am currently programming a Pong game in Python. For this, I am using the Python Turtle module. In the Pong game, the main idea is that there are two paddles (one on the left, one on the right) that hit a ball back and forth between them. This is the current state, which is running without any issues:

main.py: 

from day22.game_screen import GameScreen
from day22.paddle import Paddle

game_screen = GameScreen()
screen = game_screen.get_screen()

right_paddle = Paddle((350, 0))
left_paddle = Paddle((-350, 0))

screen.listen()
screen.onkey(key="Up", fun=right_paddle.go_up)
screen.onkey(key="Down", fun=right_paddle.go_down)
screen.onkey(key="w", fun=left_paddle.go_up)
screen.onkey(key="s", fun=left_paddle.go_down)

game_is_on = True
while game_is_on:
    screen.update()

screen.exitonclick()
paddle.py:
# some code to handle the initialization of the paddles...
game_screen.py:

from turtle import Screen

class GameScreen:
    def __init__(self):
        self.screen = Screen()
        self.screen.bgcolor("black")
        self.screen.setup(width=800, height=600)
        self.screen.title("Pong")
        self.screen.tracer(0)

    def get_screen(self):
        return self.screen

I wanted to refactor the code so that the GameScreen class inherits from the Screen class of the Turtle module. This would also make the get_screen() method redundant. So, I did the following refactoring:

game_screen.py:

from turtle import Screen

class GameScreen(Screen):
    def __init__(self):
        super().__init__()
        self.bgcolor("black")
        self.setup(width=800, height=600)
        self.title("Pong")
        self.tracer(0)
main.py:

from day22.game_screen import GameScreen
from day22.paddle import Paddle

game_screen = GameScreen()

right_paddle = Paddle((350, 0))
left_paddle = Paddle((-350, 0))

game_screen.listen()
game_screen.onkey(key="Up", fun=right_paddle.go_up)
game_screen.onkey(key="Down", fun=right_paddle.go_down)
game_screen.onkey(key="w", fun=left_paddle.go_up)
game_screen.onkey(key="s", fun=left_paddle.go_down)

game_is_on = True
while game_is_on:
    game_screen.update()

game_screen.exitonclick()

When I run this code now, I get the following error: TypeError: function() argument 'code' must be code, not str. Is the reason for the error that the Screen class in the Turtle module is marked as a singleton and therefore cannot be inherited from, or am I making another mistake?


Solution

  • Using this code you can get path to source code

    import turtle
    print( turtle.__file__ )
    

    And in source code you can see that Screen is not class but function

    def Screen():
        """Return the singleton screen object.
        If none exists at the moment, create a new one and return it,
        else return the existing one."""
        if Turtle._screen is None:
            Turtle._screen = _Screen()
        return Turtle._screen
    

    and you can't use function to create class

    You may try to use class turtle._Screen (not instances Turtle._screen)

    import turtle
    
    class GameScreen(turtle._Screen):
    

    But I don't know if it will work all time because it can have some mess in code - probably nobody expect that someone will try to create own class.


    To work without error message I had to assign class to Turtle._screen
    (like in function Screen())

    class GameScreen(turtle._Screen):
    
        def __init__(self):
            super().__init__()
            turtle.Turtle._screen = self
    

    Full working code with other changes

    I use onkeypress and onkeyrelease to set True/False on variables move_up and move_down, and function move() uses it to move turtle. And I run move() in while game_is_on.

    This way it works better on my computer - because it doesn't have to wait for repeating (pressed) keys by system.

    import time
    import turtle
    #print( turtle.__file__ )
    
    # --- classes ---
    
    class GameScreen(turtle._Screen):
        def __init__(self):
            super().__init__()
            turtle.Turtle._screen = self
            self.bgcolor("black")
            self.setup(width=800, height=600)
            self.title("Pong")
            self.tracer(0)
           
    class Paddle(turtle.Turtle):
        def __init__(self, screen, pos, key1, key2, color='white'):
            super().__init__()
            self.screen = screen
            
            self.shape('square')
            self.penup()
            self.left(90)
            self.goto(pos)
            self.color(color)
    
            self.move_up   = False
            self.move_down = False
            
            self.screen.onkeypress(  key=key1, fun=lambda:self.set_move_up(True))
            self.screen.onkeyrelease(key=key1, fun=lambda:self.set_move_up(False))
    
            self.screen.onkeypress(  key=key2, fun=lambda:self.set_move_down(True))
            self.screen.onkeyrelease(key=key2, fun=lambda:self.set_move_down(False))
            
        def set_move_up(self, value):
            print('[DEBUG] set move up:', value)    
            self.move_up = value
    
        def set_move_down(self, value):
            print('[DEBUG] set move down:', value)    
            self.move_down = value
                    
        def move(self):
            print('[DEBUG] move:', self.move_up, self.move_down)
            if self.move_up and self.ycor() < 250:
                print('[DEBUG] go up')
                self.forward(10)
            if self.move_down and self.ycor() > -250:
                print('[DEBUG] go down')
                self.backward(10)
    
    # --- functions ---
    
    def exit_game():
        global game_is_on
    
        print('[DEBUG] exit game')
        game_is_on = False
        
    # --- main code ---
    
    game_screen = GameScreen()
    
    right_paddle = Paddle(game_screen, ( 350, 0), 'Up', 'Down', 'red')
    left_paddle  = Paddle(game_screen, (-350, 0), 'w' ,'s', 'green')
    
    game_screen.listen()
    
    game_screen.onkey(key="Escape", fun=exit_game) 
    
    game_is_on = True
    while game_is_on:
        right_paddle.move()
        left_paddle.move()
        game_screen.update()
        time.sleep(0.01)   # use less CPU, and run with the same speed on different computers
    
    #print('Click screen to exit')
    turtle.color("white")
    turtle.write('Click screen to exit', True, align="center", font=('Arial', 20, 'normal'))
    game_screen.exitonclick()