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 metodABC'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 okunabilirAbstract Method: Alt Sınıf ZORUNLU Implement Eder
@abstractmethod ile işaretlenen metodlar:
Alt sınıfta mutlaka implement edilmeli.
Edilmezse alt sınıftan da nesne oluşturulamaz.
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/hAbstract 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")) # FalseInterface vs Abstract Class Python'da
Diğer dillerde (Java, C#) interface ve abstract class farklı kavramlardır:
| Özellik | Interface (Java) | Abstract Class |
|---|---|---|
| Tüm metodlar abstract mı? | Evet (Java 8 öncesi) | Hayır, karışık olabilir |
| Çoklu kalıtım | Evet (implements) | Hayır (extends, tek) |
| Attribute | Yok (sabitler hariç) | Olabilir |
| Constructor | Yok | Olabilir |
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.Protocolile 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) == 0Python'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?
| Özellik | ABC | Protocol |
|---|---|---|
| Kalıtım gerekli mi? | Evet | Hayır |
| Runtime kontrol | isinstance() her zaman | @runtime_checkable gerekir |
| Ortak davranış (somut metod) | Evet | Hayır (sadece imza) |
| Ne zaman? | Framework, zorunlu sözleşme | Tip kontrolü, duck typing |
| Yaklaşım | Nominal subtyping | Structural 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)) # TrueYaygı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.
abcmodü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:
@propertyve@abstractmethodbirlikte 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.
AI Asistan
Sorularını yanıtlamaya hazır