Weird 3D rotations in self-made engine

Issue with 3D Rotation in Python/Pygame Engine

I am building my first 3D engine using Python and Pygame for the graphics. The main 3D transformation is:

def param(x,y,z):
vec_rx = rotate_x((x,y,z), angle_x)
vec_ry = rotate_y(vec_rx , angle_y)
vec = rotate_z(vec_ry , angle_z)
return ((zoom * vec[0])/(70-vec[2]) + win_width/2,( (zoom * vec[1])/(70-vec[2]) ) + win_height/2)

70 is for the distance from the origin. The rotation is done by multiplying matrices:

def rotate_x(vec,angle):
    a = vec[0]
    b = vec[1]*math.cos(angle) - vec[2]*math.sin(angle)
    c = vec[1]*math.sin(angle) + vec[2]*math.cos(angle)
    return (a,b,c)
def rotate_y(vec,angle):
    a = vec[0]*math.cos(angle) + vec[2]*math.sin(angle)
    b = vec[1]
    c = -vec[0]*math.sin(angle) + vec[2]*math.cos(angle)
    return (a,b,c)
def rotate_z(vec,angle):
    a = vec[0]*math.cos(angle) - vec[1]*math.sin(angle)
    b = vec[0]*math.sin(angle) + vec[1]*math.cos(angle)
    c = vec[2]
    return (a,b,c)

The angles are 3 global parameters that change with keyboard/mouse input. When the angles are zero, the rotation is perfect around each axis. However, when the angles are not zero, the object does not rotate around the axis but some weird offset. This may be due to gimbal lock.

An example of the 3D engine in my project can be seen here.

Is there anything I am missing in order to make perfect rotations around the axis?

It is likely that the issue is caused by gimbal lock. To avoid this, you can try using Quaternions for your rotations instead of Euler angles. Quaternions are less prone to gimbal lock and can provide smoother and more accurate rotations in 3D space. There are several libraries available for implementing quaternions in Python, such as pyquaternion and numpy-quaternion.