← Kursa Dön
📄 Text · 15 min

Sınıf ve Nesne Kavramı

Programlama öğrenirken bir noktada "tamam, fonksiyonlarla her şeyi yapabiliyorum ama bu kod neden bu kadar dağınık?" diye düşünmeye başlarsın. Değişkenler bir yerde, fonksiyonlar başka yerde, hangi fonksiyon hangi veriyle çalışıyor belli değil. İşte Object-Oriented Programming (OOP) tam da bu karmaşayı çözmek için var.

Bu derste OOP'nin temel yapı taşlarını — sınıf ve nesne kavramlarını — sıfırdan öğreneceğiz. Merak etme, düşündüğün kadar karmaşık değil.


OOP Nedir?

OOP, yani Nesne Yönelimli Programlama, gerçek dünyayı kod ile modelleme yaklaşımıdır. Etrafına bir bak: masanın üzerindeki telefon, elindeki kalem, ekrandaki tarayıcı penceresi… Hepsi birer nesne. Her nesnenin özellikleri (rengi, boyutu, markası) ve davranışları (açılır, kapanır, yazı yazar) var.

OOP diyor ki: "Madem gerçek dünya nesnelerden oluşuyor, yazılımı da nesnelerle modelleyelim." Bu kadar basit aslında.

Prosedürel vs OOP

Şimdiye kadar büyük ihtimalle prosedürel tarzda kod yazdın. Yani fonksiyonlar tanımladın, değişkenler oluşturdun ve bunları bir sırayla çağırdın. Bu yaklaşım küçük programlar için gayet iyi çalışır. Ama program büyüdükçe şöyle sorunlar çıkar:

  • Hangi fonksiyon hangi veriyle çalışıyor? Belli değil.

  • Aynı tür veriler için aynı fonksiyonları tekrar tekrar yazıyorsun.

  • Bir yerde bir şeyi değiştirdin, başka yerde her şey bozuldu.

# Prosedürel yaklaşım — dağınık
student_name = "Ali"
student_grade = 85
student_passed = True

def print_student(name, grade, passed):
    status = "Geçti" if passed else "Kaldı"
    print(f"{name}: {grade} — {status}")

def is_honor(grade):
    return grade >= 90

print_student(student_name, student_grade, student_passed)

Bu kodda öğrenci verileri havada uçuşuyor. 50 öğrenci olsa ne yapacaksın? Her biri için ayrı değişken mi? Liste mi? Sözlük mü? Karmaşıklaşmaya başlıyor.

# OOP yaklaşım — düzenli
class Student:
    def __init__(self, name, grade):
        self.name = name
        self.grade = grade
        self.passed = grade >= 50

    def print_info(self):
        status = "Geçti" if self.passed else "Kaldı"
        print(f"{self.name}: {self.grade} — {status}")

    def is_honor(self):
        return self.grade >= 90

ali = Student("Ali", 85)
ali.print_info()

Gördün mü farkı? Veri ve davranış bir arada. Öğrencinin adı, notu ve fonksiyonları hep aynı yerde. Bu düzeni sağlayan şey sınıf kavramı.


Sınıf ve Nesne — Kurabiye Kalıbı Analojisi 🍪

OOP'nin en temel iki kavramı sınıf (class) ve nesne (object). Aralarındaki farkı anlamak için şu analojiyi düşün:

Sınıf = Kurabiye kalıbı. Kalıbın kendisi kurabiye değildir. Ama ondan kurabiye üretirsin. Kalıp yıldız şeklindeyse, ürettiğin her kurabiye yıldız şeklinde olur.

Nesne = Kalıptan çıkan kurabiye. Her kurabiye aynı şekle sahiptir (çünkü aynı kalıptan çıktı) ama farklı süslemeleri olabilir — biri çikolatalı, biri kremalı.

Programlama terimleriyle:

  • Sınıf: Bir şeyin nasıl olacağını tanımlayan şablon/blueprint.

  • Nesne: O şablondan üretilen somut bir örnek (instance).

# Sınıf = kalıp
class Cookie:
    shape = "star"  # Her kurabiye yıldız şeklinde

# Nesne = kalıptan üretilen kurabiyeler
cookie1 = Cookie()
cookie2 = Cookie()

print(cookie1.shape)  # star
print(cookie2.shape)  # star

# Ama her nesne bağımsız
cookie1.topping = "chocolate"
cookie2.topping = "cream"

print(cookie1.topping)  # chocolate
print(cookie2.topping)  # cream

Bir sınıftan istediğin kadar nesne üretebilirsin. Her nesne bağımsız bir varlıktır — birini değiştirmek diğerini etkilemez.


class Keyword ile Sınıf Tanımlama

Python'da sınıf tanımlamak için class anahtar kelimesini kullanırsın. Söz dizimi şöyle:

class ClassName:
    # sınıfın gövdesi
    pass

Birkaç kural:

  • Sınıf adları PascalCase yazılır: MyClass, StudentRecord, BankAccount. Bu bir zorunluluk değil ama Python topluluğunun güçlü bir convention'ıdır. PEP 8'e uygun kod yazmak istiyorsan buna uy.

  • pass boş bir sınıf için yer tutucu. Gövdesi olmayan sınıfta pass yazmazsanız SyntaxError alırsınız.

class Dog:
    species = "Canis familiaris"

    def bark(self):
        return "Hav hav!"

Bu sınıf bir köpeği modelliyor. species bir sınıf özelliği (class attribute), bark() ise bir metod (method). Metod, sınıfın içinde tanımlanan bir fonksiyondur. self parametresini şimdilik "nesnenin kendisi" olarak düşün, bir sonraki derste detaylıca konuşacağız.

En Basit Sınıf

class Empty:
    pass

obj = Empty()
print(type(obj))  # <class '__main__.Empty'>

Evet, pass ile bile geçerli bir sınıf oluşturabilirsin. Bu sınıftan nesne üretebilir, hatta sonradan özellik ekleyebilirsin. Python bu konuda çok esnek.


Nesne Oluşturma (Instantiation)

Sınıftan nesne üretmeye instantiation denir. Bir sınıfı fonksiyon gibi çağırdığında yeni bir nesne yaratılır:

class Car:
    def __init__(self, brand, year):
        self.brand = brand
        self.year = year

    def describe(self):
        return f"{self.year} model {self.brand}"

# Nesne oluşturma
car1 = Car("Toyota", 2020)
car2 = Car("Honda", 2022)

print(car1.describe())  # 2020 model Toyota
print(car2.describe())  # 2022 model Honda

Her Car(...) çağrısı yeni bir nesne yaratır. car1 ve car2 aynı sınıftan geliyorlar ama farklı verilere sahipler. Bu, OOP'nin güzelliği — bir kere tanımla, istediğin kadar üret.

Nesneye Sonradan Özellik Ekleme

Python dinamik bir dil olduğu için, nesneye sınıf tanımında olmayan özellikler de ekleyebilirsin:

class Person:
    pass

p = Person()
p.name = "Ayşe"
p.age = 25

print(p.name)  # Ayşe
print(p.age)   # 25

Bu çalışır ama önerilmez. Neden? Çünkü hangi nesnenin hangi özelliklere sahip olduğunu takip etmek zorlaşır. Sınıfın __init__ metodunda tüm özellikleri tanımlamak çok daha iyi bir pratiktir.

💡 İpucu: Python'da sınıf tanımlarken tüm instance attribute'ları __init__ içinde tanımla. Bu, kodunu okuyan herkesin "bu nesnenin hangi özellikleri var?" sorusuna tek bir yere bakarak cevap bulmasını sağlar.


Sınıf vs Instance

Bu ayrımı iyi anlamak çok önemli çünkü ileride "class attribute" ve "instance attribute" farkı karşına sıkça çıkacak.

Sınıf (Class): Şablonun kendisi. Bellekte tek bir yerde durur. Tüm nesneler bu şablonu paylaşır.

Instance (Nesne): Şablondan üretilen somut örnek. Her birinin bellekte ayrı bir adresi vardır.

class Circle:
    pi = 3.14159  # Class attribute — tüm daireler için aynı

    def __init__(self, radius):
        self.radius = radius  # Instance attribute — her daire için farklı

    def area(self):
        return Circle.pi * self.radius ** 2

c1 = Circle(5)
c2 = Circle(10)

print(c1.area())   # 78.53975
print(c2.area())   # 314.159

# Aynı sınıftan mı?
print(type(c1))           # <class '__main__.Circle'>
print(type(c1) == type(c2))  # True

# Ama farklı nesneler
print(c1 is c2)    # False
print(id(c1))      # farklı bellek adresi
print(id(c2))      # farklı bellek adresi

pi bir class attribute — tüm Circle nesneleri aynı pi değerini paylaşıyor. radius ise instance attribute — her dairenin kendi yarıçapı var.

type() ve isinstance()

Bir nesnenin hangi sınıftan geldiğini kontrol etmek için:

class Animal:
    pass

class Dog(Animal):
    pass

buddy = Dog()

print(type(buddy))              # <class '__main__.Dog'>
print(type(buddy) == Dog)       # True
print(type(buddy) == Animal)    # False — type() tam sınıfı kontrol eder

print(isinstance(buddy, Dog))     # True
print(isinstance(buddy, Animal))  # True — isinstance() kalıtımı da kontrol eder

type() tam eşleşme arar. isinstance() ise kalıtım zincirini de kontrol eder (kalıtımı ileride göreceğiz). Genel olarak isinstance() kullanmak daha güvenlidir.


dir() ile Nesnenin Özelliklerini Keşfetme

Python'da bir nesnenin neler yapabildiğini öğrenmek istiyorsan dir() fonksiyonu arkadaşın:

class Robot:
    def __init__(self, name):
        self.name = name

    def greet(self):
        return f"Merhaba, ben {self.name}!"

r = Robot("R2D2")
print(dir(r))

Bu çıktı oldukça uzun olacak. __init__, __str__, __eq__ gibi çift alt çizgili bir sürü şey göreceksin. Bunlar magic methods (sihirli metodlar) — Python'un arka planda her nesneye otomatik eklediği özel metodlar. İleride bunları detaylıca göreceğiz.

Şimdilik önemli olan: dir() ile bir nesnenin tüm özellik ve metodlarını listeleyebilirsin. Bu, özellikle yeni bir kütüphaneyle çalışırken çok işe yarar.

# Sadece kendi tanımladıklarımızı görelim
attrs = [a for a in dir(r) if not a.startswith('_')]
print(attrs)  # ['greet', 'name']

Alt çizgiyle başlamayanları filtreleyerek sadece "bizim" özellikleri görebilirsin.

help() Fonksiyonu

dir() özellikleri listeler ama ne işe yaradığını söylemez. help() fonksiyonu ise docstring'leri gösterir:

class Calculator:
    """Basit bir hesap makinesi sınıfı."""

    def add(self, a, b):
        """İki sayıyı toplar."""
        return a + b

    def multiply(self, a, b):
        """İki sayıyı çarpar."""
        return a * b

calc = Calculator()
help(calc)

Bu, sınıfın ve metodlarının dökümanını güzel bir formatta gösterir. Kendi sınıflarını yazarken docstring eklemeyi alışkanlık edin — gelecekteki sen teşekkür edecek.


Python'da Her Şey Nesne

Python'ın en güzel özelliklerinden biri: her şey bir nesnedir. Sayılar, string'ler, listeler, fonksiyonlar, hatta sınıfların kendisi bile birer nesne.

# Sayılar nesne
x = 42
print(type(x))        # <class 'int'>
print(x.bit_length())  # 6 — 42'nin binary gösterimi 6 bit

# String'ler nesne
s = "hello"
print(type(s))     # <class 'str'>
print(s.upper())   # HELLO

# Listeler nesne
lst = [1, 2, 3]
print(type(lst))    # <class 'list'>
lst.append(4)       # metod çağırıyoruz çünkü nesne!

# Fonksiyonlar bile nesne
def greet():
    return "Merhaba!"

print(type(greet))  # <class 'function'>
print(greet.__name__)  # greet

Bu neden önemli? Çünkü Python'da OOP öğrenirken aslında "yeni bir paradigma" öğrenmiyorsun — zaten OOP kullanıyordun! "hello".upper() yazdığında bir nesnenin metodunu çağırıyordun. Sadece farkında değildin.

Sınıflar da Nesne

Hatta sınıfların kendisi de birer nesne. Her sınıf type sınıfının bir instance'ı:

class Foo:
    pass

print(type(Foo))    # <class 'type'>
print(type(int))    # <class 'type'>
print(type(str))    # <class 'type'>

# type'ın tipi ne?
print(type(type))   # <class 'type'> — kendisinin instance'ı!

Bu biraz kafa karıştırıcı olabilir ve şu an tam anlamak zorunda değilsin. Ama şunu bil: Python'da her şeyin bir tipi var ve her tip bir sınıf. Bu tutarlılık, dilin gücünün kaynağıdır.


Nesne Kimliği: id() ve is

Her nesnenin Python'da benzersiz bir kimliği (identity) vardır. Bu kimliği id() fonksiyonuyla görebilirsin:

a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(id(a))  # 140234567890  (örnek)
print(id(b))  # 140234567950  (farklı!)
print(id(c))  # 140234567890  (a ile aynı!)

a ve b aynı değere sahip ama farklı nesneler. c ise a'nın kendisi — aynı nesneye farklı bir isim verdik.

== vs is

a = [1, 2, 3]
b = [1, 2, 3]
c = a

# == değer karşılaştırması
print(a == b)  # True — değerleri aynı

# is kimlik karşılaştırması
print(a is b)  # False — farklı nesneler
print(a is c)  # True — aynı nesne

== iki nesnenin değerinin aynı olup olmadığını kontrol eder. is ise iki değişkenin aynı nesneye mi referans verdiğini kontrol eder.

⚠️ Dikkat: is operatörünü genellikle sadece None kontrolü için kullan: if x is None. Değer karşılaştırması için her zaman == kullan. Python bazı küçük tamsayıları ve kısa stringleri cache'lediği için is beklenmedik sonuçlar verebilir.

# Python küçük tamsayıları cache'ler (-5 ile 256 arası)
x = 256
y = 256
print(x is y)  # True — cache'lenmiş

x = 257
y = 257
print(x is y)  # False — farklı nesneler (implementasyona bağlı)

Sınıfın Anatomisi

Bir sınıfı parçalarına ayıralım:

class BankAccount:
    """Basit bir banka hesabı sınıfı."""

    # Class attribute
    bank_name = "Python Bank"
    account_count = 0

    def __init__(self, owner, balance=0):
        """Yeni hesap oluşturur."""
        self.owner = owner          # Instance attribute
        self.balance = balance      # Instance attribute
        BankAccount.account_count += 1

    def deposit(self, amount):
        """Hesaba para yatırır."""
        if amount > 0:
            self.balance += amount
            return True
        return False

    def withdraw(self, amount):
        """Hesaptan para çeker."""
        if 0 < amount <= self.balance:
            self.balance -= amount
            return True
        return False

    def get_info(self):
        """Hesap bilgilerini döner."""
        return f"{self.owner} — {self.bank_name} — Bakiye: {self.balance} TL"

Bu sınıfta neler var:

  1. Docstring: Sınıfın ne yaptığını açıklayan metin.

  2. Class attributes: bank_name ve account_count — tüm nesneler tarafından paylaşılır.

  3. `__init__` metodu: Nesne oluşturulduğunda çağrılır (constructor).

  4. Instance attributes: self.owner ve self.balance — her nesneye özel.

  5. Instance methods: deposit(), withdraw(), get_info() — nesne üzerinde çalışan fonksiyonlar.

# Kullanım
acc1 = BankAccount("Ali", 1000)
acc2 = BankAccount("Ayşe", 2000)

acc1.deposit(500)
acc2.withdraw(300)

print(acc1.get_info())  # Ali — Python Bank — Bakiye: 1500 TL
print(acc2.get_info())  # Ayşe — Python Bank — Bakiye: 1700 TL

print(BankAccount.account_count)  # 2

OOP'nin 4 Temel Prensibi

OOP dört temel prensip üzerine kuruludur. Bu derste sadece genel bir bakış yapacağız — her birini ilerleyen derslerde derinlemesine işleyeceğiz.

1. Encapsulation (Kapsülleme)

Veri ve o veriyle çalışan metodları bir arada tutmak. Ayrıca veriye doğrudan erişimi kısıtlayıp, kontrollü bir arayüz sunmak.

class TemperatureSensor:
    def __init__(self):
        self._temperature = 0  # "private" — dışarıdan dokunma

    def get_temperature(self):
        return self._temperature

    def set_temperature(self, value):
        if -50 <= value <= 150:
            self._temperature = value
        else:
            raise ValueError("Geçersiz sıcaklık değeri!")

Dışarıdan _temperature'a doğrudan erişmek yerine, kontrollü metodlar kullanıyoruz. Bu sayede geçersiz değerler engellenebilir.

2. Inheritance (Kalıtım)

Bir sınıfın başka bir sınıftan özellik ve davranış devralması. Kod tekrarını azaltır.

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return "..."

class Dog(Animal):
    def speak(self):
        return "Hav hav!"

class Cat(Animal):
    def speak(self):
        return "Miyav!"

buddy = Dog("Buddy")
print(buddy.name)    # Buddy — Animal'dan miras
print(buddy.speak())  # Hav hav! — Dog'a özel

Dog ve Cat sınıfları Animal'dan miras alıyor. name özelliğini yeniden tanımlamak zorunda değiller.

3. Polymorphism (Çok Biçimlilik)

Farklı sınıfların aynı arayüzü (metod adını) farklı şekillerde uygulaması.

animals = [Dog("Rex"), Cat("Whiskers"), Dog("Max")]

for animal in animals:
    print(f"{animal.name}: {animal.speak()}")
# Rex: Hav hav!
# Whiskers: Miyav!
# Max: Hav hav!

speak() metodunu çağırıyoruz ama her nesne kendi versiyonunu çalıştırıyor. İşte polimorfizm bu — aynı isim, farklı davranış.

4. Abstraction (Soyutlama)

Karmaşık detayları gizleyip, kullanıcıya sadece gerekli arayüzü sunmak.

class EmailSender:
    def send(self, to, subject, body):
        """E-posta gönderir."""
        self._connect_to_server()
        self._authenticate()
        self._compose_message(to, subject, body)
        self._send_message()
        self._disconnect()

    def _connect_to_server(self):
        # Karmaşık bağlantı kodu...
        pass

    def _authenticate(self):
        # Karmaşık kimlik doğrulama...
        pass

    # ... diğer private metodlar

Kullanıcı sadece send() metodunu bilmek zorunda. Arka plandaki karmaşık adımlar gizlenmiş durumda.


Gerçek Dünya Örneği: Kitap Kataloğu

Tüm öğrendiklerimizi birleştiren bir örnek yapalım:

class Book:
    """Bir kitabı temsil eden sınıf."""

    total_books = 0  # Class attribute

    def __init__(self, title, author, pages, isbn):
        self.title = title
        self.author = author
        self.pages = pages
        self.isbn = isbn
        self.is_available = True
        Book.total_books += 1

    def borrow(self):
        """Kitabı ödünç alır."""
        if self.is_available:
            self.is_available = False
            return f"'{self.title}' ödünç alındı."
        return f"'{self.title}' şu an müsait değil."

    def return_book(self):
        """Kitabı iade eder."""
        self.is_available = True
        return f"'{self.title}' iade edildi."

    def get_summary(self):
        """Kitap özetini döner."""
        status = "Müsait" if self.is_available else "Ödünç verilmiş"
        return f"{self.title} — {self.author} ({self.pages} sayfa) [{status}]"
# Kullanım
book1 = Book("Python Crash Course", "Eric Matthes", 544, "978-1593279288")
book2 = Book("Clean Code", "Robert C. Martin", 464, "978-0132350884")

print(book1.get_summary())
# Python Crash Course — Eric Matthes (544 sayfa) [Müsait]

print(book1.borrow())
# 'Python Crash Course' ödünç alındı.

print(book1.borrow())
# 'Python Crash Course' şu an müsait değil.

print(book1.return_book())
# 'Python Crash Course' iade edildi.

print(f"Toplam kitap: {Book.total_books}")  # Toplam kitap: 2

Bu örnek basit ama OOP'nin gücünü gösteriyor:

  • Her kitap kendi verisini taşıyor (encapsulation).

  • Kitap ödünç alma/iade mantığı sınıfın içinde (organizasyon).

  • Yeni bir kitap eklemek tek satır (Book(...)) kadar kolay (tekrar kullanılabilirlik).


Sınıf Tasarım İpuçları

Kendi sınıflarını yazarken şu kurallara uy:

İsimlendirme

  • Sınıf adları PascalCase: StudentRecord, BankAccount

  • Metod ve attribute adları snake_case: get_balance, is_active

  • "Private" özellikler alt çizgiyle başlar: _internal_data

Tek Sorumluluk

Her sınıf tek bir şeyi iyi yapmalı. StudentDatabaseEmailerPDFGenerator gibi bir sınıf gördüysen, bir şeyler yanlış gidiyor demektir.

# Kötü — çok fazla sorumluluk
class Student:
    def calculate_gpa(self): ...
    def send_email(self): ...
    def generate_pdf(self): ...
    def connect_to_database(self): ...

# İyi — her sınıf tek sorumluluk
class Student:
    def calculate_gpa(self): ...

class EmailService:
    def send_email(self, student): ...

class ReportGenerator:
    def generate_pdf(self, student): ...

Docstring Yaz

class Rectangle:
    """Bir dikdörtgeni temsil eder.

    Attributes:
        width: Dikdörtgenin genişliği.
        height: Dikdörtgenin yüksekliği.
    """

    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        """Dikdörtgenin alanını hesaplar."""
        return self.width * self.height

Yaygın Hatalar ve Tuzaklar

1. self'i Unutmak

class Broken:
    def greet():  # self yok!
        return "Merhaba"

b = Broken()
# b.greet()  # TypeError: greet() takes 0 positional arguments but 1 was given

Instance metodu yazarken ilk parametre her zaman self olmalı. Python, nesne üzerinden metod çağrıldığında nesneyi otomatik olarak ilk argüman olarak geçirir.

2. Mutable Class Attribute Tuzağı

class Wrong:
    items = []  # Tehlike! Tüm nesneler aynı listeyi paylaşır!

    def add(self, item):
        self.items.append(item)

a = Wrong()
b = Wrong()
a.add("x")

print(b.items)  # ['x'] — Sürpriz! b'ye de eklendi!

Mutable (değiştirilebilir) değerleri class attribute olarak tanımlama. Bunları __init__ içinde instance attribute olarak tanımla:

class Right:
    def __init__(self):
        self.items = []  # Her nesnenin kendi listesi

    def add(self, item):
        self.items.append(item)

a = Right()
b = Right()
a.add("x")

print(b.items)  # [] — Doğru! Her nesne bağımsız.

⚠️ Dikkat: list, dict, set gibi mutable değerleri asla class attribute olarak kullanma. Her nesne aynı objeyi paylaşır ve bir nesnede yapılan değişiklik tüm nesneleri etkiler. Her zaman __init__ içinde tanımla.

3. Sınıf Adı ve Değişken Adı Çakışması

# Kötü
str = "hello"  # Built-in str sınıfını ezdik!
# str(42)  # TypeError! Artık str fonksiyon değil, bir string.

# Değişken adlarında built-in isimleri kullanma
# list, dict, set, type, id, input, print, ...

Ne Zaman Sınıf Yazmalısın?

Her şeyi sınıf yapmak zorunda değilsin. İşte karar rehberi:

Sınıf kullan:

  • Birbiriyle ilişkili veri ve davranışları gruplamak istiyorsan

  • Aynı yapıda birden fazla nesne oluşturacaksan

  • Nesnenin bir durumu (state) varsa ve bu durum değişiyorsa

Sınıf kullanma:

  • Sadece birkaç fonksiyonu gruplamak istiyorsan (modül kullan)

  • Durumu olmayan utility fonksiyonlar için (düz fonksiyon yeterli)

  • Tek bir instance olacaksa ve durumu yoksa (fonksiyonlar + sözlük yeterli)

# Buna sınıf gerekmez
class MathUtils:
    @staticmethod
    def add(a, b):
        return a + b

# Bu yeterli
def add(a, b):
    return a + b

💡 İpucu: Python'da "her şeyi sınıf yap" yaklaşımı (Java tarzı) pek benimsenmez. Python'da fonksiyonlar birinci sınıf vatandaştır ve çoğu zaman yeterlidir. Sınıfı gerçekten ihtiyacın olduğunda kullan.


Alıştırma: Kendi Sınıfını Yaz

Şimdi öğrendiklerini pekiştirmek için basit bir Playlist sınıfı yaz:

class Playlist:
    """Müzik çalma listesi."""

    def __init__(self, name):
        self.name = name
        self.songs = []

    def add_song(self, song):
        """Listeye şarkı ekler."""
        if song not in self.songs:
            self.songs.append(song)
            return f"'{song}' eklendi."
        return f"'{song}' zaten listede."

    def remove_song(self, song):
        """Listeden şarkı çıkarır."""
        if song in self.songs:
            self.songs.remove(song)
            return f"'{song}' çıkarıldı."
        return f"'{song}' listede bulunamadı."

    def show(self):
        """Çalma listesini gösterir."""
        if not self.songs:
            return f"'{self.name}' boş."
        header = f"🎵 {self.name} ({len(self.songs)} şarkı):"
        songs = "\n".join(f"  {i+1}. {s}" for i, s in enumerate(self.songs))
        return f"{header}\n{songs}"


# Test
playlist = Playlist("Favori Şarkılarım")
print(playlist.add_song("Bohemian Rhapsody"))
print(playlist.add_song("Stairway to Heaven"))
print(playlist.add_song("Hotel California"))
print(playlist.add_song("Bohemian Rhapsody"))  # Zaten var
print()
print(playlist.show())

Çıktı:

'Bohemian Rhapsody' eklendi.
'Stairway to Heaven' eklendi.
'Hotel California' eklendi.
'Bohemian Rhapsody' zaten listede.

🎵 Favori Şarkılarım (3 şarkı):
  1. Bohemian Rhapsody
  2. Stairway to Heaven
  3. Hotel California

Özet

  • OOP, gerçek dünyayı nesnelerle modelleme yaklaşımıdır. Veri ve davranışı bir arada tutar.

  • Sınıf (class) bir şablon, nesne (object/instance) o şablondan üretilen somut bir örnektir — kurabiye kalıbı ve kurabiye gibi.

  • class keyword ile sınıf tanımlanır, sınıfı çağırarak (MyClass()) nesne üretilir.

  • Class attribute tüm nesneler tarafından paylaşılır, instance attribute her nesneye özeldir.

  • Python'da her şey bir nesnedir — sayılar, stringler, listeler, fonksiyonlar, hatta sınıfların kendisi.

  • OOP'nin 4 prensibi: Encapsulation (kapsülleme), Inheritance (kalıtım), Polymorphism (çok biçimlilik), Abstraction (soyutlama).

  • dir() ile nesnenin tüm özelliklerini, type() ile tipini, id() ile kimliğini öğrenebilirsin.