Assorted comments inline below…
class Car:
# Front end where user login is authenticated before starting the system
def __init__(self, user, password, model, year, identification):
self.user = user # username
self.password = password # password
self.model = model # car model
self.year = year # year the car was made
self.identification = identification # user id
# Welcome message
print(f"Welcome {self.user} with ID {self.identification}. You are using car {self.model} from {self.year}.")
The print’s good for debug so you get some confirmation that the Car
has been initialised correctly. But normally __init__ is silent (as
are many functions - think of the noise!)
In real systems we try to never store passwords in the clear, or at all
really. Don’t change this now, but a real system will store not the
password but a salted hash of it - this is a string computed from the
original password which lets you confirm that a supplied password
matches it, but does not let you compute the password from the hash -
the hash is a one way lossy computation.
# Operation 1: Car Control
class Control:
speed = 0 # in kilometers per hr (km/ph)
max_speed = 120 # maximum speed before automatic braking
car_on = True # if car is started
steer_direction = math.radians # angle of steering
car_controls = ("start", "steer", "accelerate", "brake", "stop", "update") # a list of car controls
real_time_data = [] # updates about the system will be listed here
I don’t think the above does what you want.
You’re defining a set of attribute for the class itself (Control), not
each instance.
(Classes are objects too, so you can set attributes on them too.)
Normally you’d be setting some of these up in the __init__ method as
well, eg:
self.speed = 0 # in kilometers per hr (km/ph)
self.car_on = True # if car is started
self.steer_direction = math.radians # angle of steering
self.real_time_data = [] # updates about the system will be listed here
in the __init__ method.
What we do set on the class itself is attributes defining some common
defaults or other class wide features of the class. So you might start
your class like this:
class Control:
MAX_SPEED = 120 # maximum speed before automatic braking
CAR_CONTROLS = ("start", "steer", "accelerate", "brake", "stop", "update") # a list of car controls
Think about what these attributes mean: are they attributes for the
class, which apply for every instance and are not changed, or are they
attributes of an instance? The former you’d define as above. The
latter get defined on self in the __init__ method.
You can still refer to the class attributes via self, for example
self.MAX_SPEED - as long as you haven’t set that attribute on self
it will be found on self’s class. This lets you write:
if self.speed > self.MAX_SPEED:
Another odd item is this line:
steer_direction = math.radians # angle of steering
math.radians is a function accepting a value in degrees and returning
a value in radians. I’m not sure what you intended to mean here.
def start(self):
if self.car_on: # if true
print(f"Car is starting.")
else: # if false
print(f"Car is stopped.")
What’s the purpose of this function? Is it to start the car, which I
would expect to affect the value of self.car_on, or to report the
current state of the car, which is what it seems to do? i.e. printing
out whether the car is on or not.
def steer(self, steer_left, steer_right,):
if math.radians(270) <= math.radians(90): # steering angle towards the left
The condition in the if-statement is always false. 270 is not <= 90, and
the corresponding values in radians have the same relation. You at least
want the comparison in the other direction: >=. Or swap them so that
90 is on the left.
However, you’re comparing two constant values: their relation is always
fixed.
I suspect you actually want to examine the value of
self.steer_direction. You can write this test like this:
if math.radians(90) <= self.steer_direction <= math.radians(270):
because Python has this nice chained comparison so you can compare
things in one go as above.
Note that this assumes self.steer_direction is always positive, like
the degrees range 0 through 360 - you want your radians direction to be
between 0 and 2 π. This means that if you change direction you need to
make sure that the result is not negative. So if you subtract from it,
if the value goes below 0 you need it to wrap around to 2 π.
At the least, put in an assertion at the start of this function:
assert 0 <= self.steer_direction < 2 * math.pi
That will at least fail if your value gets out of range. If you don’t
know the above to be true, all the if-statements which follow can make
incorrect decisions. Thus the assertion.
assert isinstance(steer_left, object)
The assertion above will never fail. All values in Python are objects
and thus instances of object. What do you intend this assertion to
test?
def accelerate(self, speed):
if speed >= 0: # possible to speed up if speed is 0
print(f"Accelerating.") # car is speeding up
Is this really what you mean? Acceleration is change in speed. If you
intend to express whether you’re allowed to accelerate, wouldn’t you
test that you’re below the max speed, not >0 ?
def brake(self, speed):
if speed >= 120: # maximum speed has been reached, car has to break
print(f"Braking.") # car is slowing down
Ok, here you’re saying “too fast”. The 120 is what we cal a “magic
number”: a plain value in the code whose meaning isn’t explained.
At the start of the class you defined max_speed to be this value. You
should use it here:
if speed >= self.max_speed:
This makes the meaning clear, and has the added benefit that you could
change the value at the top of the class and the approriate thing would
still happen here.
def update(self): # displays real time data about the car
self.car_controls = ()
self.real_time_data = []
print(f"The state of the car is {self.car_controls}. Real time data is {self.real_time_data}")
This actually sets the attributes on self. Did you mean to do that? It
looks like this function should just contain the print() call.
# Operation 2: Environment Perception
class Perception:
present_location = []
lane = True # boolean value that states whether the car is driving within the lane
avoid = ("other car", "humans", "animals", "trees", "plants") # list of objects to avoid using LiDAR
distance = 10 # in meters, maximum distance from object
Again: which is these attributes are for the class, and which for the
instance? The instance attributes should be set on self in the
__init__ method.
Finally:
def traffic_light(self):
self.light = {} # traffic light is detected and interpreted by the camera and machine learning
if self.light == "red": # stop light
print(f"Car should stop.") # car stops
elif self.light == "yellow": # slow down
print(f"Car should slow down.") # car slows down
elif self.light == "green": # go sign
print(f"Car can move.") # car drives
what I have a chain of if/elif/elif/… I like to put a final
clause like this:
else:
raise RuntimeError(f'unhandled value for self.light: {self.light!r}')
This arranges to raise an exception is you’ve missed something in the
earlier ifs. Shouldn’t happen, but if it does you want to know rather
than have your function silently do nothing when it should have done
something.