Search code examples
pythonpython-3.xpygamecythonattributeerror

Error connected with line number and not the code. How to fix it?


I'm writing a program of self-learning cars driving to a point with pygame and cython. I think that the problem is somehow caused by cython compiler and not pygame, because there is nothing connected with pygame in that method. However, when I cythonize the .pyd file, no error is given. Also if I change the code by any lines, for example, add a line before line 99, where the error is, the error line will not move by one, it will stay in 99 line. So, the error is somehow connected with the line number and not code. How is that possible? Here is the class and the method I got an error in:

cdef class Car:
    cdef:
        double speed, car_angle, wheel_angle
        Point position
        Polygon shape

    def __init__(self, position=(0.0, 0.0), speed=0.5, car_angle=0.0, wheel_angle=0.0):
        self.position = Point(*position)
        self.shape = Polygon(self.position, CAR_SHAPE[0], CAR_SHAPE[1], self.car_angle)
        self.speed = speed
        self.car_angle = car_angle  # from -pi to pi
        self.wheel_angle = wheel_angle  # from -MAX_WHEEL_ANGLE to MAX_WHEEL_ANGLE

An error:

Traceback (most recent call last):
  File "main.pyx", line 83, in main.Population.__init__
    self.cars = [Car(position=CAR_START_POS, car_angle=random.random() * 2 - 1) for _ in range(amount)]
  File "main.pyx", line 99, in main.Car.__init__
    self.car_angle = car_angle  # from -pi to pi
AttributeError: 'main.Car' object has no attribute 'shape'
Exception ignored in: 'main.main'
Traceback (most recent call last):
  File "main.pyx", line 83, in main.Population.__init__
    self.cars = [Car(position=CAR_START_POS, car_angle=random.random() * 2 - 1) for _ in range(amount)]
  File "main.pyx", line 99, in main.Car.__init__
    self.car_angle = car_angle  # from -pi to pi
AttributeError: 'main.Car' object has no attribute 'shape'

Edit: As you can see the error is in line 99, but line 99 is

self.car_angle = car_angle  # from -pi to pi

and it has nothing to do with attribute shape. I have got all defines for the class Car before the init function. And attribute shape is also in there. Then Why does it say, that there are no such attribute?

I'm using python 3.8, cython 0.29.21 Here is the whole code, if you need:

import random
from math import sin, cos, pi, sqrt

import numpy as np
import pygame

pygame.init()
cdef:
    tuple BG_COLOR = (10, 10, 20)
    tuple SIZE = (800, 600)
    double MAX_WHEEL_ANGLE = 0.872665
    tuple CAR_COLOR = (200, 200, 200)
    tuple CAR_SHAPE = (23, 10)
    tuple CAR_START_POS = (SIZE[0] / 2, SIZE[1])
    clock = pygame.time.Clock()
    font = pygame.font.SysFont("Arial", 18)
    screen = pygame.display.set_mode(SIZE, pygame.NOFRAME)

def update_fps():
    cdef:
        str fps = str(int(clock.get_fps()))
        fps_text = font.render(fps, True, pygame.Color("coral"))
    return fps_text

cdef class Point():
    cdef double x, y
    cdef shift_x, shift_y

    def __init__(self, double x, double y):
        self.x = x
        self.y = y

    def __str__(self):
        return self.x, self.y

    cdef tuple to_array(self):
        return (self.x, self.y)

    cdef void rotate(self, Point center, double sine, double cosine):
        self.x = cosine * (self.x - center.x) + sine * (self.y - center.y) + center.x
        self.y = -sine * (self.x - center.x) + cosine * (self.y - center.y) + center.y

cdef class Polygon():
    cdef:
        Point center, p1, p2, p3, p4
        double length, width, radius, angle, sine, cosine

    def __init__(self, center: Point, length: double, width: double, angle: double):
        self.center = center
        self.length = length
        self.width = width
        self.radius = sqrt(width ** 2 + length ** 2)
        self.p1 = Point(center.x - width / 2, center.y + length / 2)
        self.p2 = Point(center.x + width / 2, center.y + length / 2)
        self.p3 = Point(center.x + width / 2, center.y - length / 2)
        self.p4 = Point(center.x - width / 2, center.y - length / 2)

        self.angle = angle

        sine, cosine = sin(pi - self.angle), cos(pi - self.angle)
        self.p1.x, self.p1.y = self.shift_x(self.p1.x, self.p1.y, sine, cosine), self.shift_y(self.p1.x, self.p1.y, sine, cosine)
        self.p2.x, self.p2.y = self.shift_x(self.p2.x, self.p2.y, sine, cosine), self.shift_y(self.p2.x, self.p2.y, sine, cosine)
        self.p3.x, self.p3.y = self.shift_x(self.p3.x, self.p3.y, sine, cosine), self.shift_y(self.p3.x, self.p3.y, sine, cosine)
        self.p4.x, self.p4.y = self.shift_x(self.p4.x, self.p4.y, sine, cosine), self.shift_y(self.p4.x, self.p4.y, sine, cosine)


    cdef shift_x(self, double x, double y, sine, cosine):
        return cosine * (x - self.center.x) + sine * (y - self.center.y) + self.center.x

    cdef shift_y(self, double x, double y, sine, cosine):
        return -sine * (x - self.center.x) + cosine * (y - self.center.y) + self.center.y

    def __str__(self):
        return (self.p1, self.p2, self.p3, self.p4)

    cdef tuple to_array(self):
        return self.p1.to_array(), self.p2.to_array(), self.p3.to_array(), self.p4.to_array()

cdef class Population:
    cdef list cars

    def __init__(self, amount: int):
        self.cars = [Car(position=CAR_START_POS, car_angle=random.random() * 2 - 1) for _ in range(amount)]

    cdef void go(self):
        for car in self.cars:
            car.go()

cdef class Car:
    cdef:
        double speed, car_angle, wheel_angle
        Point position
        Polygon shape

    def __init__(self, position=(0.0, 0.0), speed=0.5, car_angle=0.0, wheel_angle=0.0):
        self.position = Point(*position)
        self.shape = Polygon(self.position, CAR_SHAPE[0], CAR_SHAPE[1], self.car_angle)
        self.speed = speed
        self.car_angle = car_angle  # from -pi to pi
        self.wheel_angle = wheel_angle  # from -MAX_WHEEL_ANGLE to MAX_WHEEL_ANGLE

    cdef void go(self):
        self.position.y -= np.cos(self.car_angle) * self.speed
        self.position.x += np.sin(self.car_angle) * self.speed
        self.car_angle += self.wheel_angle
        self.draw()

    cdef void draw(self):
        pygame.draw.polygon(screen, CAR_COLOR, self.shape.to_array(), 3)
        pygame.draw.circle(screen, (255, 0, 0), (self.position.x, self.position.y), 1)

cdef class DNA:
    pass

cpdef void main():
    cdef bint close_program = False
    cdef Population cars = Population(1000)

    while not close_program:
        screen.fill(BG_COLOR)
        cars.go()
        screen.blit(update_fps(), (10, 0))
        clock.tick(60)
        pygame.display.flip()

if __name__ == '__main__':
    main()

EDIT2: most likely the problem is with python and cython versions. The .pyd file it creates is main.cp39-win_amd64, so it means that cython creates a file for python 3.9 and I use 3.8, but tried to go from 3.8 to 3.9. Unfortunately, I couldn't just move all downloaded libraries from 3.8 to 3.9, so, kept using the old version. If you know how to move all the libraries, help me. Otherwise, I have to somehow say cython to create files for python 3.8. And I also don't know how to do that. So, there are 2 possible solutions:

  1. Move all libraries to python 3.9
  2. Say cython compiler to work with 3.8 version

Solution

  • The problem was with cython and python versions. Now I'm doing this project on python 3.9. I downloaded all libraries I will need for this project, but it would be a lot better if someone would help me with moving all python 3.8 libraries to 3.9 without manually downloading them.