Słowo kluczowe self to jeden z fundamentalnych elementów programowania obiektowego w Pythonie, który często budzi wątpliwości wśród początkujących programistów. To specjalny parametr, który automatycznie przekazuje referencję do bieżącej instancji obiektu, umożliwiając dostęp do jego atrybutów i metod.
Czym jest self w Pythonie
self to konwencjonalna nazwa parametru, która reprezentuje konkretną instancję klasy. Gdy wywołujemy metodę na obiekcie, Python automatycznie przekazuje referencję do tego obiektu jako pierwszy argument metody. Dzięki temu możemy odwoływać się do atrybutów i metod danej instancji z wnętrza klasy.
Kluczowe cechy self –
- Pierwszy parametr każdej metody instancji;
- Nie jest słowem kluczowym – to tylko konwencja nazewnicza;
- Umożliwia dostęp do atrybutów i metod konkretnej instancji;
- Automatycznie przekazywane przez interpreter Pythona.
Podstawy działania self
Aby zrozumieć działanie self, przyjrzyjmy się prostemu przykładowi:
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
return f"{self.name} szczeka!"
def get_info(self):
return f"Pies {self.name} ma {self.age} lat"
# Tworzenie instancji
my_dog = Dog("Burek", 3)
print(my_dog.bark()) # Burek szczeka!
print(my_dog.get_info()) # Pies Burek ma 3 lat
W powyższym przykładzie self pozwala każdej metodzie na dostęp do atrybutów name i age konkretnej instancji obiektu.
Metoda __init__ i rola self
Metoda __init__ to specjalny konstruktor w Pythonie, który jest wywoływany automatycznie podczas tworzenia nowej instancji klasy. Parametr self w tej metodzie jest szczególnie ważny:
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner # Przypisanie do instancji
self.balance = balance # Przypisanie do instancji
self.transactions = [] # Lista transakcji dla tej instancji
def deposit(self, amount):
self.balance += amount
self.transactions.append(f"Wpłata: {amount}")
def withdraw(self, amount):
if self.balance >= amount:
self.balance -= amount
self.transactions.append(f"Wypłata: {amount}")
else:
print("Niewystarczające środki")
# Tworzenie różnych kont
account1 = BankAccount("Jan Kowalski", 1000)
account2 = BankAccount("Anna Nowak", 500)
account1.deposit(200)
account2.withdraw(100)
print(f"{account1.owner}: {account1.balance} zł") # Jan Kowalski: 1200 zł
print(f"{account2.owner}: {account2.balance} zł") # Anna Nowak: 400 zł
Atrybuty instancji vs atrybuty klasy
Self odnosi się wyłącznie do atrybutów instancji, nie do atrybutów klasy. Oto różnica:
class Car:
# Atrybut klasy – wspólny dla wszystkich instancji
wheels = 4
def __init__(self, brand, model):
# Atrybuty instancji – unikalne dla każdej instancji
self.brand = brand
self.model = model
self.mileage = 0
def drive(self, distance):
self.mileage += distance
print(f"{self.brand} {self.model} przejechał {distance} km")
@classmethod
def get_wheels(cls):
return cls.wheels # Dostęp do atrybutu klasy
car1 = Car("Toyota", "Corolla")
car2 = Car("BMW", "X5")
car1.drive(100) # Toyota Corolla przejechał 100 km
car2.drive(200) # BMW X5 przejechał 200 km
print(car1.mileage) # 100
print(car2.mileage) # 200
print(Car.wheels) # 4
Zaawansowane zastosowania self
Wywoływanie innych metod instancji
class Calculator:
def __init__(self):
self.history = []
def add(self, a, b):
result = a + b
self._save_operation(f"{a} + {b} = {result}")
return result
def multiply(self, a, b):
result = a * b
self._save_operation(f"{a} * {b} = {result}")
return result
def _save_operation(self, operation):
# Prywatna metoda wywoływana przez inne metody
self.history.append(operation)
def get_history(self):
return self.history
calc = Calculator()
calc.add(5, 3)
calc.multiply(4, 2)
print(calc.get_history()) # ['5 + 3 = 8', '4 * 2 = 8']
Modyfikowanie atrybutów dynamicznie
class DynamicObject:
def __init__(self):
pass
def add_attribute(self, name, value):
setattr(self, name, value)
def get_attribute(self, name):
return getattr(self, name, "Atrybut nie istnieje")
def list_attributes(self):
return [attr for attr in dir(self) if not attr.startswith('_')]
obj = DynamicObject()
obj.add_attribute("color", "czerwony")
obj.add_attribute("size", "duży")
print(obj.get_attribute("color")) # czerwony
print(obj.list_attributes()) # ['add_attribute', 'color', 'get_attribute', 'list_attributes', 'size']
Częste błędy związane z self
Zapominanie o self w definicji metody
# BŁĘDNIE – brak parametru self
class WrongClass:
def method():
# Błąd!
return "To nie zadziała"
# POPRAWNIE
class CorrectClass:
def method(self):
return "To zadziała"
Nieprawidłowe wywoływanie metod
class Example:
def __init__(self, value):
self.value = value
def get_value(self):
return self.value
def double_value(self):
# BŁĘDNIE – bezpośrednie wywołanie bez self
# return get_value() * 2
# POPRAWNIE – wywołanie przez self
return self.get_value() * 2
obj = Example(5)
print(obj.double_value()) # 10
Alternatywy dla nazwy self
Choć self to silna konwencja, technicznie można użyć innej nazwy:
class TestClass:
def __init__(this, value):
# Można użyć 'this' zamiast 'self'
this.value = value
def show(instance):
# Lub 'instance'
print(f"Wartość: {instance.value}")
obj = TestClass(42)
obj.show() # Wartość: 42
Jednak zawsze używaj self – to standardowa konwencja w Pythonie, której przestrzeganie zapewnia czytelność kodu dla innych programistów.
Praktyczne wskazówki
Najlepsze praktyki używania self –
- Zawsze używaj
selfjako pierwszego parametru metod instancji; - Nie przekazuj
selfexplicite przy wywoływaniu metod; - Wykorzystuj
selfdo przechowywania stanu obiektu; - Oddzielaj atrybuty instancji od atrybutów klasy;
- Dokumentuj metody, które modyfikują stan obiektu przez
self.
Kompletny przykład – system zarządzania studentami
class Student:
# Atrybut klasy all_students = []
all_students = []
def __init__(self, name, student_id):
self.name = name
self.student_id = student_id
self.grades = {}
self.is_enrolled = True
# Dodanie studenta do listy wszystkich studentów
Student.all_students.append(self)
def add_grade(self, subject, grade):
if subject not in self.grades:
self.grades[subject] = []
self.grades[subject].append(grade)
self._update_status()
def get_average(self, subject=None):
if subject:
if subject in self.grades:
return sum(self.grades[subject]) / len(self.grades[subject])
return 0
all_grades = []
for grades_list in self.grades.values():
all_grades.extend(grades_list)
return sum(all_grades) / len(all_grades) if all_grades else 0
def _update_status(self):
# Prywatna metoda aktualizująca status studenta
avg = self.get_average()
self.is_enrolled = avg >= 2.0
def get_info(self):
status = "aktywny" if self.is_enrolled else "nieaktywny"
return f"Student {self.name} (ID: {self.student_id}) – Status: {status}, Średnia: {self.get_average():.2f}"
@classmethod
def get_student_count(cls):
return len(cls.all_students)
# Użycie klasy
student1 = Student("Jan Kowalski", "12345")
student2 = Student("Anna Nowak", "67890")
student1.add_grade("Matematyka", 4.5)
student1.add_grade("Polski", 3.0)
student2.add_grade("Matematyka", 2.0)
print(student1.get_info())
print(student2.get_info())
print(f"Liczba studentów: {Student.get_student_count()}")
Zrozumienie self jest kluczowe dla efektywnego programowania obiektowego w Pythonie. Ten mechanizm pozwala na tworzenie elastycznych, modularnych aplikacji, gdzie każdy obiekt może zarządzać swoim własnym stanem i zachowaniem. Dzięki właściwemu wykorzystaniu self, kod staje się bardziej czytelny, łatwiejszy w utrzymaniu i zgodny z zasadami programowania obiektowego.