Słowo kluczowe self w Pythonie – zastosowanie i dobre praktyki

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 self jako pierwszego parametru metod instancji;
  • Nie przekazuj self explicite przy wywoływaniu metod;
  • Wykorzystuj self do 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.

Programista i twórca serwisu Creative Coding, absolwent Politechniki Warszawskiej (WEiTI). Od 10+ lat łączy front‑end, grafikę generatywną i narzędzia dla twórców; opublikował 120+ projektów i artykułów, prowadził warsztaty dla 2 000+ uczestników. Pracuje z JavaScriptem, Three.js, P5.js i GLSL, bada wydajność i dokumentuje procesy, tworząc praktyczne przewodniki dla osób łączących kod z obrazem, dźwiękiem i interakcją.
Zostaw komentarz

Komentarze

Brak komentarzy. Dlaczego nie rozpoczniesz dyskusji?

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *