Если кратко и по простому, то дело обстоит так:
Обычно класс создают для того, чтобы потом создать некоторое количество экземпляров этого класса.
При написании класса нужно как-то указать, что какие-то манипуляции нужно производить именно над конкретными экземплярами, а не над самим классом.
Для этого в функциях (кроме некоторых исключений) первым аргументом пишут имя (по традиции это как раз слово "self", хотя на самом деле там можно указать любое корректное имя). В результате, когда в функции интерпретатор видит имя self, он понимает, что речь идёт именно об одном конкретном объекте класса.
Пример:
class A:
# У класса есть атрибут "x"
x = 1
def change_one(self, value):
# Эта функция меняет атрибут "x" только у того экземпляра класса,
# для которого она вызвана
self.x = value
def change_all(self, value):
# Эта функция меняет атрибут "x" у самого класса
# Изменение отразится на всех экземплярах, у которых нет своего атрибута "x"
A.x = value
a1 = A()
a2 = A()
a3 = A()
print(a1.x, a2.x, a3.x)
# Выведет: 1 1 1
# На самом деле, у экземпляров пока нет своего атрибута "x",
# но они получают его значение из класса
a1.change_one(5)
print(a1.x, a2.x, a3.x)
# Выведет: 5 1 1
# Теперь у а1 есть свой атрибут x, потому что функция change_one через self
# создала его только для того экземпляра, для которого её вызвали
# a2 и a3 по прежнему не имеют своего атрибута "x" и получают его значение из класса
a2.change_all(2)
print(a1.x, a2.x, a3.x)
# Выведет: 5 2 2
# Изменился атрибут класса, и поэтому значение изменилось для всех элементов,
# которые не имеют своего атрибута "x", а вынуждены брать его из класса
a1.change_all(7)
print(a1.x, a2.x, a3.x)
# Выведет: 5 7 7
# Совершенно всё равно, из какого экземпляра вы запускаете change_all.
# В этой функции не используется self (в отличие от change_one),
# а значит результат её вызова никак не связан с конкретным экземпляром