← Kursa Dön
📄 Text · 12 min

Abstract Classes ve ABC

Şimdiye kadar parent sınıflarda raise NotImplementedError ile "bu metodu alt sınıf implement etsin" dedik. Ama bu zorunluluk sadece çalışma zamanında (runtime) ortaya çıkıyordu — yani programı çalıştırmadan bilemezdin. Peki daha sınıf tanımlanırken bu zorlamayı yapabilir miyiz?

Evet. İşte abstract class bunun için var.


Abstract Class Nedir?

Abstract class (soyut sınıf), doğrudan nesne oluşturulamayan, alt sınıflar için bir şablon/sözleşme görevi gören sınıftır. İçinde abstract method'lar — yani gövdesi olmayan, alt sınıfın zorunlu olarak implement etmesi gereken metodlar — bulunur.

Sözleşme Analojisi 📝

Abstract class'ı bir iş sözleşmesi gibi düşün. Sözleşme diyor ki: "Bu pozisyonda çalışmak istiyorsan şu görevleri YAPACAKSIN." Görevlerin nasıl yapılacağı sana kalmış ama yapılması zorunlu.

  • Sözleşme = Abstract class

  • Görev maddeleri = Abstract methods

  • Çalışan = Concrete (somut) alt sınıf

  • Görevleri yerine getirme = Method implementation

# Sözleşme olmadan — runtime'da patlıyor
class Shape:
    def area(self):
        raise NotImplementedError  # Çalışma zamanında hata

class Circle(Shape):
    pass  # area() implement etmedi ama Python şikayet etmiyor

c = Circle()
# c.area()  # NotImplementedError — AMA nesne oluşturulabildi!
# Abstract class ile — nesne oluşturulamıyor bile
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Circle(Shape):
    pass  # area() implement etmedi

# c = Circle()  # TypeError: Can't instantiate abstract class Circle
#                # with abstract method area
# Nesne oluşturulamadı! Hata daha erken yakalandı.

abc Modülü: ABC ve abstractmethod

Python'da abstract class oluşturmak için abc (Abstract Base Classes) modülünü kullanırız:

from abc import ABC, abstractmethod

class Animal(ABC):
    """Soyut hayvan sınıfı — doğrudan oluşturulamaz."""

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

    @abstractmethod
    def speak(self):
        """Her hayvan ses çıkarmalı — alt sınıf ZORUNLU implement eder."""
        pass

    @abstractmethod
    def move(self):
        """Her hayvan hareket etmeli."""
        pass

    def breathe(self):
        """Somut metod — tüm hayvanlar nefes alır."""
        return f"{self.name} nefes alıyor..."

    def __str__(self):
        return f"{self.__class__.__name__}('{self.name}')"
# Abstract class'tan nesne oluşturulamaz
# a = Animal("Rex")  # TypeError!

# Alt sınıf TÜM abstract methodları implement etmeli
class Dog(Animal):
    def speak(self):
        return f"{self.name}: Hav hav!"

    def move(self):
        return f"{self.name} koşuyor!"

class Cat(Animal):
    def speak(self):
        return f"{self.name}: Miyav!"

    def move(self):
        return f"{self.name} süzülüyor!"

# Eksik implementation
class Broken(Animal):
    def speak(self):
        return "..."
    # move() implement edilmedi!

# b = Broken("?")  # TypeError: Can't instantiate abstract class Broken
#                   # with abstract method move

# Doğru kullanım
dog = Dog("Rex")
cat = Cat("Boncuk")

print(dog.speak())     # Rex: Hav hav!
print(cat.move())      # Boncuk süzülüyor!
print(dog.breathe())   # Rex nefes alıyor... — somut metod

ABC'nin İki Kullanım Yolu

# Yol 1: ABC'den miras alma (önerilen)
class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

# Yol 2: metaclass ile (eski yol)
class Shape(metaclass=ABCMeta):
    @abstractmethod
    def area(self):
        pass

# İkisi aynı şeyi yapar, ABC daha okunabilir

Abstract Method: Alt Sınıf ZORUNLU Implement Eder

@abstractmethod ile işaretlenen metodlar:

  1. Alt sınıfta mutlaka implement edilmeli.

  2. Edilmezse alt sınıftan da nesne oluşturulamaz.

  3. Abstract method'un gövdesi olabilir — alt sınıf super() ile çağırabilir.

from abc import ABC, abstractmethod

class Serializer(ABC):
    @abstractmethod
    def serialize(self, data):
        """Veriyi serileştirir. Alt sınıf implement etmeli."""
        pass  # Gövde boş

    @abstractmethod
    def deserialize(self, text):
        """Serileştirilmiş veriyi geri çevirir."""
        pass

class JsonSerializer(Serializer):
    def serialize(self, data):
        import json
        return json.dumps(data, ensure_ascii=False)

    def deserialize(self, text):
        import json
        return json.loads(text)

class CsvSerializer(Serializer):
    def serialize(self, data):
        if not data:
            return ""
        headers = ",".join(data[0].keys())
        rows = [",".join(str(v) for v in row.values()) for row in data]
        return f"{headers}\n" + "\n".join(rows)

    def deserialize(self, text):
        lines = text.strip().split("\n")
        headers = lines[0].split(",")
        return [dict(zip(headers, line.split(","))) for line in lines[1:]]
# Kullanım
data = [
    {"name": "Ali", "age": "25"},
    {"name": "Veli", "age": "30"},
]

json_s = JsonSerializer()
csv_s = CsvSerializer()

# Aynı arayüz, farklı format
json_text = json_s.serialize(data)
csv_text = csv_s.serialize(data)

print("JSON:")
print(json_text)
print("\nCSV:")
print(csv_text)

Abstract Method ile Default Implementation

Abstract method'un gövdesi olabilir. Alt sınıf super() ile bu default'u kullanabilir:

from abc import ABC, abstractmethod

class Logger(ABC):
    @abstractmethod
    def log(self, message):
        """Default: mesajı formatlar."""
        from datetime import datetime
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        return f"[{timestamp}] {message}"

class ConsoleLogger(Logger):
    def log(self, message):
        formatted = super().log(message)  # Abstract method'un default'unu çağır
        print(formatted)

class FileLogger(Logger):
    def __init__(self, filename):
        self.filename = filename

    def log(self, message):
        formatted = super().log(message)
        # Dosyaya yazma simülasyonu
        print(f"FILE({self.filename}): {formatted}")

cl = ConsoleLogger()
cl.log("Sistem başladı")
# [2024-01-15 14:30:25] Sistem başladı

fl = FileLogger("app.log")
fl.log("Hata oluştu!")
# FILE(app.log): [2024-01-15 14:30:25] Hata oluştu!

Abstract Property

@abstractmethod ile @property'yi birleştirebilirsin:

from abc import ABC, abstractmethod

class Vehicle(ABC):
    def __init__(self, brand):
        self._brand = brand

    @property
    @abstractmethod
    def fuel_type(self):
        """Yakıt türü — alt sınıf tanımlamalı."""
        pass

    @property
    @abstractmethod
    def max_speed(self):
        """Maksimum hız — alt sınıf tanımlamalı."""
        pass

    def info(self):
        return f"{self._brand} — {self.fuel_type}, Max: {self.max_speed}km/h"

class ElectricCar(Vehicle):
    @property
    def fuel_type(self):
        return "Elektrik"

    @property
    def max_speed(self):
        return 250

class DieselTruck(Vehicle):
    @property
    def fuel_type(self):
        return "Dizel"

    @property
    def max_speed(self):
        return 120

tesla = ElectricCar("Tesla")
truck = DieselTruck("Volvo")

print(tesla.info())  # Tesla — Elektrik, Max: 250km/h
print(truck.info())  # Volvo — Dizel, Max: 120km/h

Abstract Class Method ve Static Method

from abc import ABC, abstractmethod

class Database(ABC):
    @classmethod
    @abstractmethod
    def connect(cls, connection_string):
        """Veritabanına bağlan."""
        pass

    @staticmethod
    @abstractmethod
    def validate_query(query):
        """SQL sorgusunu doğrula."""
        pass

class PostgreSQL(Database):
    @classmethod
    def connect(cls, connection_string):
        return f"PostgreSQL bağlantısı: {connection_string}"

    @staticmethod
    def validate_query(query):
        dangerous = ["DROP", "TRUNCATE", "DELETE FROM"]
        return not any(d in query.upper() for d in dangerous)

print(PostgreSQL.connect("localhost:5432"))  # PostgreSQL bağlantısı: ...
print(PostgreSQL.validate_query("SELECT * FROM users"))  # True
print(PostgreSQL.validate_query("DROP TABLE users"))      # False

Interface vs Abstract Class Python'da

Diğer dillerde (Java, C#) interface ve abstract class farklı kavramlardır:

ÖzellikInterface (Java)Abstract Class
Tüm metodlar abstract mı?Evet (Java 8 öncesi)Hayır, karışık olabilir
Çoklu kalıtımEvet (implements)Hayır (extends, tek)
AttributeYok (sabitler hariç)Olabilir
ConstructorYokOlabilir

Python'da bu ayrım yoktur. Python'da:

  • Abstract class hem abstract hem somut metod içerebilir.

  • Çoklu kalıtım desteklenir.

  • Interface kavramı duck typing ve typing.Protocol ile karşılanır.

from abc import ABC, abstractmethod

# "Interface" gibi — sadece abstract method'lar
class Printable(ABC):
    @abstractmethod
    def to_string(self) -> str:
        pass

# "Abstract class" gibi — karışık
class Collection(ABC):
    def __init__(self):
        self._items = []

    @abstractmethod
    def add(self, item):
        pass

    def count(self):  # Somut metod
        return len(self._items)

    def is_empty(self):  # Somut metod
        return len(self._items) == 0

Python'da ikisini de ABC ile yaparsın. "Interface" istiyorsan tüm metodları abstract yap. "Abstract class" istiyorsan karışık bırak.


Pratik: PaymentProcessor

Gerçek bir e-ticaret senaryosu:

from abc import ABC, abstractmethod
from datetime import datetime

class PaymentProcessor(ABC):
    """Ödeme işlemci soyut sınıfı."""

    def __init__(self, merchant_id):
        self.merchant_id = merchant_id
        self._transactions = []

    @abstractmethod
    def charge(self, amount, currency="TL"):
        """Ödeme al. Alt sınıf implement eder."""
        pass

    @abstractmethod
    def refund(self, transaction_id, amount=None):
        """İade yap. Alt sınıf implement eder."""
        pass

    @property
    @abstractmethod
    def provider_name(self):
        """Sağlayıcı adı."""
        pass

    def _log_transaction(self, tx_type, amount, status):
        """İşlemi kaydet — ortak davranış."""
        tx = {
            "id": f"TX-{len(self._transactions) + 1:04d}",
            "type": tx_type,
            "amount": amount,
            "status": status,
            "provider": self.provider_name,
            "timestamp": datetime.now().isoformat(),
        }
        self._transactions.append(tx)
        return tx["id"]

    def get_transactions(self):
        return self._transactions.copy()

    def total_charged(self):
        return sum(
            tx["amount"] for tx in self._transactions
            if tx["type"] == "charge" and tx["status"] == "success"
        )

    def __str__(self):
        return f"{self.provider_name} (Merchant: {self.merchant_id})"


class CreditCardProcessor(PaymentProcessor):
    """Kredi kartı ile ödeme."""

    def __init__(self, merchant_id, card_number, cvv):
        super().__init__(merchant_id)
        self._card_number = card_number
        self._cvv = cvv

    @property
    def provider_name(self):
        return "CreditCard"

    @property
    def masked_card(self):
        return f"****-****-****-{self._card_number[-4:]}"

    def charge(self, amount, currency="TL"):
        # Basit doğrulama
        if amount <= 0:
            raise ValueError("Miktar pozitif olmalı!")
        if amount > 50000:
            tx_id = self._log_transaction("charge", amount, "declined")
            return {"status": "declined", "reason": "Limit aşıldı", "tx_id": tx_id}

        tx_id = self._log_transaction("charge", amount, "success")
        return {
            "status": "success",
            "tx_id": tx_id,
            "card": self.masked_card,
            "amount": f"{amount} {currency}",
        }

    def refund(self, transaction_id, amount=None):
        tx_id = self._log_transaction("refund", amount or 0, "success")
        return {"status": "refunded", "tx_id": tx_id}


class PayPalProcessor(PaymentProcessor):
    """PayPal ile ödeme."""

    def __init__(self, merchant_id, email):
        super().__init__(merchant_id)
        self.email = email

    @property
    def provider_name(self):
        return "PayPal"

    def charge(self, amount, currency="TL"):
        if amount <= 0:
            raise ValueError("Miktar pozitif olmalı!")

        # PayPal komisyon simülasyonu
        commission = amount * 0.029 + 0.30
        net_amount = amount - commission

        tx_id = self._log_transaction("charge", amount, "success")
        return {
            "status": "success",
            "tx_id": tx_id,
            "email": self.email,
            "gross": f"{amount} {currency}",
            "commission": f"{commission:.2f} {currency}",
            "net": f"{net_amount:.2f} {currency}",
        }

    def refund(self, transaction_id, amount=None):
        tx_id = self._log_transaction("refund", amount or 0, "success")
        return {"status": "refunded", "tx_id": tx_id, "to": self.email}


class BankTransferProcessor(PaymentProcessor):
    """Banka havalesi ile ödeme."""

    def __init__(self, merchant_id, iban):
        super().__init__(merchant_id)
        self.iban = iban

    @property
    def provider_name(self):
        return "BankTransfer"

    def charge(self, amount, currency="TL"):
        if amount <= 0:
            raise ValueError("Miktar pozitif olmalı!")

        tx_id = self._log_transaction("charge", amount, "pending")
        return {
            "status": "pending",
            "tx_id": tx_id,
            "iban": self.iban,
            "note": "Havale onayı bekleniyor (1-2 iş günü)",
        }

    def refund(self, transaction_id, amount=None):
        tx_id = self._log_transaction("refund", amount or 0, "pending")
        return {"status": "pending", "tx_id": tx_id, "note": "İade 3-5 iş günü"}
# Polimorfik kullanım
def process_payment(processor: PaymentProcessor, amount: float):
    """Hangi processor olduğunu bilmiyor!"""
    print(f"\n💳 {processor.provider_name} ile {amount} TL ödeme:")
    result = processor.charge(amount)
    for key, value in result.items():
        print(f"  {key}: {value}")
    return result

# Farklı ödeme yöntemleri
cc = CreditCardProcessor("M001", "4532015112830366", "123")
pp = PayPalProcessor("M001", "ali@mail.com")
bt = BankTransferProcessor("M001", "TR330006100519786457841326")

for processor in [cc, pp, bt]:
    process_payment(processor, 1500)

# Toplam işlem
print(f"\nKredi kartı toplam: {cc.total_charged()} TL")

typing.Protocol: Structural Subtyping

Python 3.8+ ile gelen typing.Protocol, abstract class'a alternatif bir yaklaşımdır. Kalıtım gerektirmez — sadece doğru metodlara sahip olmak yeterli (duck typing'in formalize edilmiş hali):

from typing import Protocol, runtime_checkable

@runtime_checkable
class Closeable(Protocol):
    def close(self) -> None: ...

class DatabaseConnection:
    def close(self):
        print("DB bağlantısı kapatıldı.")

class FileHandle:
    def close(self):
        print("Dosya kapatıldı.")

class RandomClass:
    pass

# isinstance kontrolü çalışır!
db = DatabaseConnection()
fh = FileHandle()
rc = RandomClass()

print(isinstance(db, Closeable))  # True — close() metodu var
print(isinstance(fh, Closeable))  # True — close() metodu var
print(isinstance(rc, Closeable))  # False — close() metodu yok

def cleanup(resources: list[Closeable]):
    for r in resources:
        r.close()

cleanup([db, fh])

ABC vs Protocol: Ne Zaman Hangisi?

ÖzellikABCProtocol
Kalıtım gerekli mi?EvetHayır
Runtime kontrolisinstance() her zaman@runtime_checkable gerekir
Ortak davranış (somut metod)EvetHayır (sadece imza)
Ne zaman?Framework, zorunlu sözleşmeTip kontrolü, duck typing
YaklaşımNominal subtypingStructural subtyping

💡 İpucu: ABC kullan: Alt sınıfların belirli metodları implement etmesini zorla istiyorsan ve ortak davranış (somut metodlar) paylaşmak istiyorsan. Protocol kullan: Sadece "bu metodu olan her nesne" demek istiyorsan ve kalıtım bağımlılığı istemiyorsan.


Abstract Class ile register()

Bazen mevcut bir sınıfı abstract class'ın "sanal" alt sınıfı olarak kaydetmek istersin:

from abc import ABC, abstractmethod

class Iterable(ABC):
    @abstractmethod
    def __iter__(self):
        pass

# list zaten __iter__ var, ama Iterable'dan miras almıyor
print(isinstance([], Iterable))  # True — Python bunu otomatik tanır

# Kendi sınıflarımız için register
class MyContainer(ABC):
    @abstractmethod
    def __contains__(self, item):
        pass

class FrozenSet:
    """Miras almadan register ile abstract alt sınıf."""
    def __contains__(self, item):
        return item in {1, 2, 3}

MyContainer.register(FrozenSet)
print(isinstance(FrozenSet(), MyContainer))  # True

Yaygın Hatalar

1. Abstract Class'tan Nesne Oluşturmaya Çalışmak

from abc import ABC, abstractmethod

class Base(ABC):
    @abstractmethod
    def do_something(self):
        pass

# b = Base()  # TypeError!

2. @abstractmethod'u Yanlış Sırada Yazmak

# ❌ YANLIŞ sıra
class Wrong(ABC):
    @property
    @abstractmethod  # abstractmethod ÖNCe olmalı? Hayır, altta olmalı!
    def x(self): pass

# ✅ DOĞRU sıra — @abstractmethod en içte
class Right(ABC):
    @property
    @abstractmethod
    def x(self): pass
    # Aslında bu konuda Python esnek, ama resmi belgeleme
    # @abstractmethod'u iç decorator olarak önerir.

3. ABC Olduğunu Unutup Nesne Oluşturmak

# İyi bir hata mesajı al diye __str__ yaz
class Shape(ABC):
    @abstractmethod
    def area(self):
        """Alanı hesaplar."""
        pass

# 6 ay sonra:
# s = Shape()  # TypeError: Can't instantiate abstract class Shape
# Bu hata mesajı GÜZEL — hemen neden olduğunu anlarsın

Özet

  • Abstract class, doğrudan nesne oluşturulamayan, alt sınıflar için şablon/sözleşme görevi gören sınıftır — iş sözleşmesi gibi.

  • abc modülünden `ABC` ve `@abstractmethod` kullanılır. Alt sınıf tüm abstract methodları implement etmek zorundadır.

  • Abstract class hem abstract hem somut (concrete) metodlar içerebilir. Somut metodlar ortak davranış sağlar.

  • Abstract property: @property ve @abstractmethod birlikte kullanılır.

  • Python'da ayrı bir "interface" kavramı yoktur. "Saf interface" istiyorsan tüm metodları abstract yap.

  • `typing.Protocol` structural subtyping sağlar — kalıtım gerekmez, doğru metodlara sahip olmak yeterli.

  • ABC: Zorunlu sözleşme + ortak davranış. Protocol: Esnek tip kontrolü + duck typing formalizasyonu.