Class Student - create dunder or magic methods in Python

Dunder or magic methods in Python are the methods having two prefix and suffix underscores in the method name. Dunder here means "Double Under (Underscores)". These are commonly used for operator overloading.
Few examples for magic methods are: __init__, __len__, __repr__ etc.

Example with the same Student class:

class Student:
  def __init__(self, first, last, courses=None):
    self.first_name = first
    self.last_name = last
    if courses == None:
      self.courses = []
    else:
      self.courses = courses
  def add_course(self, course):
    if course not in self.courses:
      self.courses.append(course)
    else:
      print(f"{self.first_name} is already \
enrolled in the {course} course")
  def remove_course(self, course):
    if course in self.courses:
      self.courses.remove(course)
    else:
      print(f"{course} not found")
  def __len__(self):
    return len(self.courses)
  def __repr__(self):
    return f"Student('{self.first_name}', '{self.last_name}', {self.courses})"
  def __str__(self):
    return f"First name: {self.first_name.capitalize()}\n\
Last name: {self.last_name.capitalize()}\n\
Courses: {', '.join(map(str.capitalize, self.courses))}"

courses1 = ['python', 'java', 'javascript']
courses2 = ['java', 'rails', 'c']

dmitri = Student("dmitri", "telinov", courses1)
john = Student("john", "doe", courses2)

print(dmitri)
print(john)

print(len(dmitri))
print(len(john))

print(repr(dmitri))
print(repr(john))
Output:
First name: Dmitri
Last name: Telinov
Courses: Python, Java, Javascript
First name: John
Last name: Doe
Courses: Java, Rails, C
3
3
Student('dmitri', 'telinov', ['python', 'java', 'javascript'])
Student('john', 'doe', ['java', 'rails', 'c'])