← Kursa Dön
📄 Text · 15 min

Enum Modülü: Sabit Değerlerin Zarif Yönetimi

Giriş

Programlama yaparken sık sık sabit değerlerle çalışırız: haftanın günleri, HTTP durum kodları, sipariş durumları, renk seçenekleri... Çoğu geliştirici alışkanlıkla status = 1, color = "red" gibi sıradan değişkenler kullanır. Ancak bu yaklaşım büyük projelerde ciddi sorunlara yol açar.

Python'un enum modülü tam da bu sorunu çözmek için tasarlanmıştır. Bu derste Enum, IntEnum, StrEnum, Flag, auto() ve @unique araçlarını gerçek örneklerle inceleyeceğiz.


1. Neden Enum Kullanmalıyız? — Magic Number Problemi

Şu kodu bir düşünün:

def process_order(order):
    if order.status == 0:
        print("Beklemede")
    elif order.status == 1:
        print("Ödendi")
    elif order.status == 2:
        print("Kargoya verildi")
    elif order.status == 3:
        print("Teslim edildi")

Bu kodda 0, 1, 2, 3 rakamları magic number (sihirli sayı) olarak adlandırılır. Kodu okuyan biri "2 ne demek?" diye sormak zorunda kalır. Üstelik yanlışlıkla order.status = 99 yazarsanız Python hiçbir hata vermez — hata runtime'da sessizce kaybolur.

Bir de string sabitleri deneyelim:

STATUS_PENDING = "pending"
STATUS_PAID = "paid"
STATUS_SHIPPED = "shipped"

def process_order(order):
    if order.status == STATUS_PENDING:
        print("Beklemede")
    elif order.status == "piad":  # typo! Kimse fark etmez
        print("Ödendi")

String sabitleri biraz daha okunabilir ama typo (yazım hatası) riski devam eder. "piad" yazdığınızda Python yine sessiz kalır. Ayrıca bu sabitler arasında mantıksal bir bağlantı yoktur — hepsi bağımsız değişkenlerdir.

Enum tam da bu noktada devreye girer. Sabit değerleri bir grup altında toplar, tip güvenliği sağlar ve okunabilirliği artırır.

from enum import Enum

class OrderStatus(Enum):
    PENDING = 0
    PAID = 1
    SHIPPED = 2
    DELIVERED = 3

def process_order(order):
    if order.status == OrderStatus.PENDING:
        print("Beklemede")
    elif order.status == OrderStatus.PAID:
        print("Ödendi")

Artık OrderStatus.PIAD yazmaya çalışırsanız Python anında AttributeError fırlatır. Magic number'lar gitti, yerine anlamlı isimler geldi. Kodunuz hem okunabilir hem güvenli.


2. Temel Enum Tanımlama

Bir enum tanımlamak için enum modülünden Enum sınıfını import edip ondan türetme yaparsınız:

from enum import Enum

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3
    YELLOW = 4

Bu kadar basit. Color artık dört üyesi olan bir enum tipidir. Her üye (RED, GREEN, BLUE, YELLOW) hem bir isme hem de bir değere sahiptir.

Trafik Lambası Analojisi 🚦

Enum'u anlamanın en güzel yolu trafik lambasını düşünmektir. Bir trafik lambası yalnızca üç durumdan birinde olabilir: kırmızı, sarı veya yeşil. Dördüncü bir durum yoktur. Aynı anda iki durumda olamaz (Flag hariç, ona geleceğiz). Ve "biraz kırmızımsı" diye bir şey söz konusu değildir — ya kırmızıdır ya değildir.

from enum import Enum

class TrafficLight(Enum):
    RED = "dur"
    YELLOW = "hazırlan"
    GREEN = "geç"

current = TrafficLight.RED
print(current)          # TrafficLight.RED
print(current.value)    # dur
print(current.name)     # RED

İşte enum'lar tam olarak budur: sonlu, sabit, birbirinden farklı durumlar kümesi. Trafik lambası gibi — ne eksik ne fazla, tam olarak tanımladığınız kadar durum vardır.

Enum üyeleri singleton (tekil) nesnelerdir. Yani TrafficLight.RED her zaman aynı nesneyi ifade eder. Bu, karşılaştırmaları hem güvenli hem hızlı kılar.


3. Enum Üyeleri: name ve value Erişimi

Her enum üyesinin iki temel özelliği vardır: name (isim) ve value (değer).

from enum import Enum

class Planet(Enum):
    MERCURY = 1
    VENUS = 2
    EARTH = 3
    MARS = 4

planet = Planet.EARTH
print(planet.name)    # EARTH (string)
print(planet.value)   # 3 (int)
print(type(planet))   # <enum 'Planet'>

name her zaman bir string'dir ve enum üyesinin tanımlandığı ismi döndürür. value ise atadığınız değerdir — int, string, tuple, hatta başka bir nesne olabilir.

Değerden Enum'a Erişim

Elinizde bir değer varsa ve bunu enum üyesine dönüştürmek istiyorsanız, enum sınıfını çağırmanız yeterlidir:

from enum import Enum

class HttpStatus(Enum):
    OK = 200
    NOT_FOUND = 404
    SERVER_ERROR = 500

# Değerden enum üyesine
status = HttpStatus(404)
print(status)        # HttpStatus.NOT_FOUND
print(status.name)   # NOT_FOUND

# İsimden enum üyesine (köşeli parantez)
status2 = HttpStatus["OK"]
print(status2)       # HttpStatus.OK
print(status2.value) # 200

HttpStatus(404) yazdığınızda Python, value'su 404 olan üyeyi bulur ve döndürür. Eğer böyle bir değer yoksa ValueError fırlatır. HttpStatus["OK"] ise isme göre erişim sağlar — bulunamazsa KeyError alırsınız.

💡 İpucu: API'den gelen verileri enum'a çevirirken try/except kullanmayı unutmayın. Olmayan bir değer için HttpStatus(999)ValueError, olmayan bir isim için HttpStatus["TEAPOT"]KeyError alırsınız.


4. Enum Iteration ve Karşılaştırma

Enum sınıfları iterable'dır (üzerinde döngü kurulabilir). Bu, tüm üyeleri listelemek veya bir seçenek menüsü oluşturmak için çok kullanışlıdır.

from enum import Enum

class Season(Enum):
    SPRING = 1
    SUMMER = 2
    AUTUMN = 3
    WINTER = 4

# Tüm üyeler üzerinde döngü
for season in Season:
    print(f"{season.name} = {season.value}")
# SPRING = 1
# SUMMER = 2
# AUTUMN = 3
# WINTER = 4

# Üye listesi
print(list(Season))
# [<Season.SPRING: 1>, <Season.SUMMER: 2>, ...]

Karşılaştırma: is ve ==

Enum üyeleri hem is (identity) hem == (equality) ile karşılaştırılabilir. Ancak sıralama operatörleri (<, >, <=, >=) standart Enum için desteklenmez.

from enum import Enum

class Direction(Enum):
    NORTH = "N"
    SOUTH = "S"
    EAST = "E"
    WEST = "W"

d = Direction.NORTH

# Identity karşılaştırma (önerilen)
print(d is Direction.NORTH)    # True
print(d is Direction.SOUTH)    # False

# Equality karşılaştırma
print(d == Direction.NORTH)    # True
print(d == "N")                # False! Enum != string

Dikkat edin: Direction.NORTH == "N" ifadesi False döndürür. Enum üyeleri kendi değerleriyle (value) doğrudan karşılaştırılmaz — bu bilinçli bir tasarım kararıdır ve tip güvenliğini korur.

⚠️ Dikkat: Standart Enum sınıfında <, > gibi sıralama operatörleri TypeError verir. Sıralama yapmanız gerekiyorsa ya IntEnum kullanın ya da sorted(items, key=lambda x: x.value) ile sıralayın.


5. IntEnum — Sayısal Enum

IntEnum, int ile Enum'un birleşimidir. Üyeleri gerçek birer integer gibi davranır — aritmetik ve karşılaştırma operatörleri çalışır.

from enum import IntEnum

class Priority(IntEnum):
    LOW = 1
    MEDIUM = 2
    HIGH = 3
    CRITICAL = 4

# Sıralama operatörleri çalışır
print(Priority.LOW < Priority.HIGH)      # True
print(Priority.CRITICAL >= Priority.HIGH) # True

# Integer ile karşılaştırma da çalışır
print(Priority.MEDIUM == 2)    # True
print(Priority.HIGH > 2)       # True

IntEnum üyeleri integer ile doğrudan karşılaştırılabilir çünkü int'ten türetilmiştir. Bu özellik veritabanı veya API entegrasyonlarında çok işe yarar.

from enum import IntEnum

class ExitCode(IntEnum):
    SUCCESS = 0
    ERROR = 1
    PERMISSION_DENIED = 2
    NOT_FOUND = 127

# Matematiksel işlemlerde kullanılabilir
code = ExitCode.ERROR
print(code + 10)      # 11 (int döner, enum değil!)
print(type(code + 10)) # <class 'int'>

# Fonksiyonlara int olarak geçirilebilir
import sys
# sys.exit(ExitCode.SUCCESS)  # 0 ile çıkar

Ancak bir uyarı: aritmetik işlemlerin sonucu int döner, IntEnum değil. Yani ExitCode.ERROR + 10 ifadesinin tipi int'tir.

⚠️ Dikkat: IntEnum kullanırken tip güvenliğinden ödün verirsiniz. Priority.HIGH == 3 ifadesi True döner — bu bazen istenmeyen karşılaştırmalara yol açabilir. Eğer sıralama gerekmiyorsa standart Enum tercih edin.


6. StrEnum (Python 3.11+) — String Bazlı Enum

Python 3.11 ile gelen StrEnum, IntEnum'un string versiyonudur. Üyeleri gerçek birer string gibi davranır.

from enum import StrEnum

class Color(StrEnum):
    RED = "red"
    GREEN = "green"
    BLUE = "blue"

# String ile doğrudan karşılaştırma
print(Color.RED == "red")     # True
print(Color.GREEN.upper())    # GREEN

# String fonksiyonlarında kullanılabilir
print(f"Selected: {Color.BLUE}")  # Selected: blue
print("red" in [c.value for c in Color])  # True

StrEnum özellikle API response'ları, JSON key'leri ve konfigürasyon değerleri için idealdir. String ile doğrudan karşılaştırılabilmesi entegrasyonları kolaylaştırır.

from enum import StrEnum

class Environment(StrEnum):
    DEV = "development"
    STAGING = "staging"
    PROD = "production"

# JSON verisinden gelen string ile eşleştirme
config_env = "production"  # API'den geldi
if config_env == Environment.PROD:
    print("Dikkat! Production ortamındasınız!")

# String concatenation çalışır
env = Environment.DEV
print("Running in " + env)  # Running in development

Python 3.11 öncesi için StrEnum yoktur ama class MyStrEnum(str, Enum) şeklinde çoklu kalıtım (multiple inheritance) yaparak aynı davranışı elde edebilirsiniz.


7. Flag ve IntFlag — Bitwise Combinable Enum

Bazen bir nesnenin aynı anda birden fazla durumda olması gerekir. Örneğin bir dosyanın hem okunabilir hem yazılabilir olması, veya bir kullanıcının hem admin hem moderatör yetkisine sahip olması. İşte Flag ve IntFlag bu senaryolar için tasarlanmıştır.

from enum import Flag, auto

class Permission(Flag):
    READ = auto()      # 1
    WRITE = auto()     # 2
    EXECUTE = auto()   # 4
    DELETE = auto()    # 8

# Birden fazla izin birleştirilebilir (bitwise OR)
user_perms = Permission.READ | Permission.WRITE
print(user_perms)            # Permission.READ|WRITE
print(Permission.READ in user_perms)   # True
print(Permission.DELETE in user_perms) # False

Flag üyelerinin değerleri 2'nin kuvvetleri olmalıdır (1, 2, 4, 8...). auto() bunu otomatik olarak yapar. Birleştirme | (OR) operatörüyle, kontrol in anahtar kelimesiyle yapılır.

IntFlag — Integer ile Uyumlu Flag

IntFlag, Flag'in integer ile uyumlu versiyonudur. Tıpkı IntEnum gibi, integer değerlerle doğrudan karşılaştırma ve aritmetik yapabilirsiniz.

from enum import IntFlag, auto

class UnixPermission(IntFlag):
    NONE = 0
    EXECUTE = 1    # 001
    WRITE = 2      # 010
    READ = 4       # 100

    # Klasik Unix izin grupları
    RW = READ | WRITE           # 6 (110)
    RX = READ | EXECUTE         # 5 (101)
    RWX = READ | WRITE | EXECUTE # 7 (111)

# Integer ile karşılaştırma
perms = UnixPermission(6)  # RW
print(perms)                       # UnixPermission.READ|WRITE
print(UnixPermission.READ in perms) # True
print(perms == 6)                  # True

# chmod 755 gibi düşünün
owner = UnixPermission(7)   # RWX
group = UnixPermission(5)   # RX
others = UnixPermission(5)  # RX
print(f"Owner: {owner}, Group: {group}, Others: {others}")

Flag ve IntFlag arasındaki fark, Enum ve IntEnum arasındaki farkla aynıdır: IntFlag integer ile doğrudan karşılaştırılabilir, Flag ise yalnızca kendi üyeleriyle karşılaştırılır.


8. auto() ile Otomatik Değer Atama

Her enum üyesine elle değer atamak bazen gereksizdir. Özellikle değerlerin ne olduğu önemli değilse, sadece birbirinden farklı olması yeterliyse auto() kullanabilirsiniz.

from enum import Enum, auto

class Direction(Enum):
    NORTH = auto()   # 1
    SOUTH = auto()   # 2
    EAST = auto()    # 3
    WEST = auto()    # 4

for d in Direction:
    print(f"{d.name} = {d.value}")
# NORTH = 1
# SOUTH = 2
# EAST = 3
# WEST = 4

auto() varsayılan olarak 1'den başlayarak artan integer değerler atar. Flag içinde kullanıldığında ise 2'nin kuvvetlerini kullanır (1, 2, 4, 8...).

from enum import Enum, auto, Flag

class TaskStatus(Enum):
    TODO = auto()        # 1
    IN_PROGRESS = auto() # 2
    REVIEW = auto()      # 3
    DONE = auto()        # 4

class Permission(Flag):
    READ = auto()     # 1
    WRITE = auto()    # 2
    EXECUTE = auto()  # 4
    ADMIN = auto()    # 8

print(TaskStatus.DONE.value)     # 4
print(Permission.EXECUTE.value)  # 4 (2'nin kuvveti!)

auto() Davranışını Özelleştirme

_generate_next_value_ metodunu override ederek auto()'nun ürettiği değerleri kontrol edebilirsiniz:

from enum import Enum, auto

class LowercaseEnum(Enum):
    """auto() üye adını küçük harfle döndürür."""
    @staticmethod
    def _generate_next_value_(name, start, count, last_values):
        return name.lower()

class Color(LowercaseEnum):
    RED = auto()      # "red"
    GREEN = auto()    # "green"
    BLUE = auto()     # "blue"

print(Color.RED.value)    # red
print(Color.GREEN.value)  # green

Bu teknik özellikle API veya veritabanı ile çalışırken çok kullanışlıdır. Üye isimlerini otomatik olarak küçük harfli string'lere dönüştürebilirsiniz.

💡 İpucu: auto() ile Flag kullandığınızda değerler otomatik olarak 2'nin kuvvetleri olur (1, 2, 4, 8...). Bu sayede bitwise operasyonlar sorunsuz çalışır. Elle değer atarken bu kurala uymayı unutmayın!


9. @unique Decorator

Varsayılan olarak Python, aynı değere sahip birden fazla enum üyesine izin verir. İkinci üye birincinin alias'ı (takma adı) olur:

from enum import Enum

class Shape(Enum):
    SQUARE = 2
    RECTANGLE = 2    # alias — SQUARE'a eşit
    DIAMOND = 3
    RHOMBUS = 3      # alias — DIAMOND'a eşit

print(Shape.SQUARE is Shape.RECTANGLE)  # True!
print(Shape(2))  # Shape.SQUARE (ilk tanımlanan döner)
print(list(Shape))
# [Shape.SQUARE, Shape.DIAMOND] — alias'lar listede GÖRÜNMEZ

Gördüğünüz gibi RECTANGLE, SQUARE'ın alias'ı haline gelir. Iteration'da alias'lar listelenmez. Bu bazen istenen bir davranış olabilir, ama çoğu zaman bir hata göstergesidir.

Eğer her değerin benzersiz olmasını garantilemek istiyorsanız @unique decorator'ünü kullanın:

from enum import Enum, unique

@unique
class Weekday(Enum):
    MONDAY = 1
    TUESDAY = 2
    WEDNESDAY = 3
    THURSDAY = 4
    FRIDAY = 5
    SATURDAY = 6
    SUNDAY = 7

# Bu hata verir:
try:
    @unique
    class BrokenDay(Enum):
        MON = 1
        MONDAY = 1  # Aynı değer!
except ValueError as e:
    print(e)
    # duplicate values found in <enum 'BrokenDay'>: MONDAY -> MON

@unique decorator'ü sınıf oluşturulurken kontrol yapar. Aynı değere sahip birden fazla üye bulursa ValueError fırlatır. Bu, büyük enum'larda yanlışlıkla aynı değerin kullanılmasını engeller.

from enum import Enum, unique

@unique
class ErrorCode(Enum):
    SUCCESS = 0
    INVALID_INPUT = 100
    NOT_FOUND = 404
    TIMEOUT = 408
    SERVER_ERROR = 500
    # INTERNAL_ERROR = 500  # Bu satırı açarsanız ValueError alırsınız

Genel kural olarak: alias'lara bilinçli olarak ihtiyacınız yoksa her zaman @unique kullanın. Bu, gelecekteki hataları önler.


10. Enum'da Metot Tanımlama

Enum sınıfları normal Python sınıfları gibi metot içerebilir. Bu, enum üyelerine davranış eklemenin güçlü bir yoludur.

from enum import Enum

class Planet(Enum):
    MERCURY = (3.303e+23, 2.4397e6)
    VENUS = (4.869e+24, 6.0518e6)
    EARTH = (5.976e+24, 6.37814e6)
    MARS = (6.421e+23, 3.3972e6)

    def __init__(self, mass, radius):
        self.mass = mass      # kg
        self.radius = radius  # metre

    @property
    def surface_gravity(self):
        G = 6.67300E-11  # m³ kg⁻¹ s⁻²
        return G * self.mass / (self.radius ** 2)

    def weight_on(self, earth_weight):
        """Dünya'daki ağırlığınızı bu gezegendeki ağırlığa çevirir."""
        return earth_weight * self.surface_gravity / Planet.EARTH.surface_gravity

# Kullanım
print(f"Mars yerçekimi: {Planet.MARS.surface_gravity:.2f} m/s²")
weight_on_mars = Planet.MARS.weight_on(75)
print(f"75 kg → Mars'ta {weight_on_mars:.1f} kg")

Bu örnekte her gezegenin kütlesi ve yarıçapı tuple olarak saklanır. __init__ metodu bu değerleri ayrıştırır, surface_gravity property'si yerçekimini hesaplar.

Daha basit bir örnek:

from enum import Enum

class Suit(Enum):
    """İskambil kağıdı türleri."""
    HEARTS = "♥"
    DIAMONDS = "♦"
    CLUBS = "♣"
    SPADES = "♠"

    @property
    def is_red(self):
        return self in (Suit.HEARTS, Suit.DIAMONDS)

    @property
    def is_black(self):
        return not self.is_red

    def describe(self):
        color = "Kırmızı" if self.is_red else "Siyah"
        return f"{self.name} ({self.value}) — {color}"

for suit in Suit:
    print(suit.describe())
# HEARTS (♥) — Kırmızı
# DIAMONDS (♦) — Kırmızı
# CLUBS (♣) — Siyah
# SPADES (♠) — Siyah

Enum'a metot ekleyerek hem veriyi hem davranışı tek bir yerde toplamış olursunuz. Bu, nesne yönelimli programlamanın temel ilkelerinden biridir.


11. Enum ve Pattern Matching (match-case)

Python 3.10 ile gelen match-case (yapısal eşleme), enum'larla mükemmel çalışır. if-elif zincirlerine kıyasla çok daha temiz ve okunabilir kod yazmanızı sağlar.

from enum import Enum

class Command(Enum):
    QUIT = "quit"
    SAVE = "save"
    LOAD = "load"
    NEW = "new"

def handle_command(cmd: Command):
    match cmd:
        case Command.QUIT:
            print("Çıkılıyor...")
        case Command.SAVE:
            print("Kaydediliyor...")
        case Command.LOAD:
            print("Yükleniyor...")
        case Command.NEW:
            print("Yeni dosya oluşturuluyor...")
        case _:
            print("Bilinmeyen komut!")

handle_command(Command.SAVE)  # Kaydediliyor...

match-case ile enum kullandığınızda her case dalında enum üyesini doğrudan yazarsınız. Wildcard (_) ile bilinmeyen durumları yakalarsınız.

from enum import Enum

class Season(Enum):
    SPRING = 1
    SUMMER = 2
    AUTUMN = 3
    WINTER = 4

def season_advice(season: Season) -> str:
    match season:
        case Season.SPRING:
            return "🌸 Alerjinize dikkat edin!"
        case Season.SUMMER:
            return "☀️ Bol su için, güneş kremi sürün."
        case Season.AUTUMN:
            return "🍂 Yağmurluk almayı unutmayın."
        case Season.WINTER:
            return "❄️ Kalın giyinin, yollar kaygan olabilir."

for s in Season:
    print(f"{s.name}: {season_advice(s)}")

Pattern matching'in güzel yanlarından biri, IDE ve linter'ların eksik case'leri yakalayabilmesidir. Enum'a yeni bir üye ekleyip match-case'e eklemeyi unutursanız, statik analiz araçları sizi uyarabilir.


12. Gerçek Dünya Örneği: Sipariş Durumu Yönetimi

Şimdi tüm öğrendiklerimizi bir araya getirelim. E-ticaret uygulamalarında en yaygın kullanım alanlarından biri: sipariş durumu yönetimi.

Bir sipariş şu aşamalardan geçer: PENDING → PAID → SHIPPED → DELIVERED. Her geçiş belirli kurallara tabidir — örneğin "PENDING" durumundaki bir sipariş doğrudan "DELIVERED" olamaz.

from enum import Enum, unique, auto
from datetime import datetime

@unique
class OrderStatus(Enum):
    PENDING = "pending"
    PAID = "paid"
    SHIPPED = "shipped"
    DELIVERED = "delivered"
    CANCELLED = "cancelled"
    REFUNDED = "refunded"

    @property
    def label_tr(self):
        """Türkçe etiket döndürür."""
        labels = {
            OrderStatus.PENDING: "Beklemede",
            OrderStatus.PAID: "Ödendi",
            OrderStatus.SHIPPED: "Kargoda",
            OrderStatus.DELIVERED: "Teslim Edildi",
            OrderStatus.CANCELLED: "İptal Edildi",
            OrderStatus.REFUNDED: "İade Edildi",
        }
        return labels[self]

    @property
    def allowed_transitions(self):
        """Bu durumdan geçilebilecek durumları döndürür."""
        transitions = {
            OrderStatus.PENDING: {OrderStatus.PAID, OrderStatus.CANCELLED},
            OrderStatus.PAID: {OrderStatus.SHIPPED, OrderStatus.REFUNDED},
            OrderStatus.SHIPPED: {OrderStatus.DELIVERED},
            OrderStatus.DELIVERED: {OrderStatus.REFUNDED},
            OrderStatus.CANCELLED: set(),
            OrderStatus.REFUNDED: set(),
        }
        return transitions[self]

    def can_transition_to(self, new_status):
        """Bu durumdan hedef duruma geçiş mümkün mü?"""
        return new_status in self.allowed_transitions

Şimdi bu enum'u kullanan bir Order sınıfı yazalım:

class Order:
    def __init__(self, order_id: str, customer: str):
        self.order_id = order_id
        self.customer = customer
        self.status = OrderStatus.PENDING
        self.history = [(OrderStatus.PENDING, datetime.now())]

    def transition_to(self, new_status: OrderStatus):
        """Sipariş durumunu güvenli şekilde değiştirir."""
        if not isinstance(new_status, OrderStatus):
            raise TypeError(f"OrderStatus bekleniyor, {type(new_status)} geldi")

        if not self.status.can_transition_to(new_status):
            raise ValueError(
                f"Geçersiz geçiş: {self.status.label_tr} → {new_status.label_tr}. "
                f"İzin verilenler: {[s.label_tr for s in self.status.allowed_transitions]}"
            )

        old_status = self.status
        self.status = new_status
        self.history.append((new_status, datetime.now()))
        print(f"[{self.order_id}] {old_status.label_tr} → {new_status.label_tr}")

    def __repr__(self):
        return f"Order({self.order_id}, status={self.status.label_tr})"

Ve kullanım:

# Sipariş oluştur
order = Order("ORD-2024-001", "Ahmet")
print(order)  # Order(ORD-2024-001, status=Beklemede)

# Normal akış: PENDING → PAID → SHIPPED → DELIVERED
order.transition_to(OrderStatus.PAID)       # ✓ Beklemede → Ödendi
order.transition_to(OrderStatus.SHIPPED)    # ✓ Ödendi → Kargoda
order.transition_to(OrderStatus.DELIVERED)  # ✓ Kargoda → Teslim Edildi

# Geçersiz geçiş deneyelim
try:
    order2 = Order("ORD-2024-002", "Mehmet")
    order2.transition_to(OrderStatus.DELIVERED)  # PENDING → DELIVERED?
except ValueError as e:
    print(f"Hata: {e}")
    # Hata: Geçersiz geçiş: Beklemede → Teslim Edildi.
    # İzin verilenler: ['Ödendi', 'İptal Edildi']

Bu örnekte enum'un gücünü görüyorsunuz:

  • Tip güvenliği: Sadece OrderStatus değerleri kabul edilir, typo imkansız.

  • Geçiş kuralları: Her durumdan hangi durumlara geçilebileceği enum içinde tanımlı.

  • Okunabilirlik: OrderStatus.SHIPPED ifadesi status = 2'den çok daha anlaşılır.

  • Bakım kolaylığı: Yeni bir durum eklemek istediğinizde tek bir yeri güncellersiniz.

Bu yapıyı section 11'deki match-case ile birleştirerek notify_customer gibi fonksiyonlar da yazabilirsiniz — her durum için farklı bildirim mesajları üreten temiz bir pattern matching örneği.


13. Enum vs String Sabitleri vs Dict — Ne Zaman Hangisi?

Bu soru çok sık sorulur. Üç yaklaşımı karşılaştıralım:

String Sabitleri

# Basit string sabitleri
STATUS_ACTIVE = "active"
STATUS_INACTIVE = "inactive"
STATUS_DELETED = "deleted"

# Avantajlar: Basit, import gerektirmez
# Dezavantajlar: Typo riski, gruplandırma yok,
#   IDE desteği sınırlı, iteration yok
user_status = "actve"  # Typo — hata vermez!

String sabitleri en basit yaklaşımdır. Küçük script'lerde veya hızlı prototiplerde işe yarar. Ancak büyük projelerde typo riski ve gruplandırma eksikliği ciddi sorunlara yol açar.

Dictionary Sabitleri

# Dictionary ile sabitler
STATUSES = {
    "active": "Aktif",
    "inactive": "Pasif",
    "deleted": "Silinmiş",
}

# Avantajlar: Key-value eşleştirme, iteration
# Dezavantajlar: Runtime'da değiştirilebilir,
#   tip güvenliği yok, IDE autocomplete zayıf
STATUSES["hacked"] = "Hacklenmiş"  # Kolayca bozulabilir

Dictionary'ler key-value eşleştirmesi gerektiğinde kullanışlıdır. Ancak mutable (değiştirilebilir) oldukları için sabit değer garantisi vermezler. Yanlışlıkla bir key eklenebilir veya silinebilir.

Enum

from enum import Enum, unique

@unique
class Status(Enum):
    ACTIVE = "active"
    INACTIVE = "inactive"
    DELETED = "deleted"

    @property
    def label(self):
        labels = {
            Status.ACTIVE: "Aktif",
            Status.INACTIVE: "Pasif",
            Status.DELETED: "Silinmiş",
        }
        return labels[self]

# Avantajlar: Tip güvenliği, immutable, IDE desteği,
#   iteration, metot desteği, pattern matching
# Dezavantajlar: Biraz daha verbose
Status.ACTIVE.label   # "Aktif"
Status("active")      # Status.ACTIVE

Karar Tablosu

KriterString SabitleriDictEnum
Tip güvenliği
Typo koruması
IDE autocomplete⚠️
Immutable⚠️
Iteration
Metot desteği
Pattern matching⚠️
Basitlik⚠️
Performans

Ne Zaman Hangisini Kullanmalı?

  • String sabitleri: Küçük script'ler, 2-3 değer, hızlı prototip.

  • Dictionary: Key-value eşleştirmesi birincil ihtiyaçsa, veriler runtime'da değişebiliyorsa.

  • Enum: Sonlu/sabit değer kümesi, tip güvenliği, okunabilirlik, birden fazla yerde kullanım, pattern matching.

💡 İpucu: Bir sabit grubunu 2'den fazla yerde kullanıyorsanız enum'a geçmeyi düşünün. Başlangıçta biraz daha fazla kod yazarsınız ama uzun vadede bakım maliyetiniz düşer, hata oranınız azalır.


Özet

  • Enum, sonlu ve sabit değerler kümesini temsil eder. Magic number'ları ortadan kaldırarak kodunuzu okunabilir ve güvenli hale getirir.

  • IntEnum ve StrEnum, sırasıyla int ve str ile uyumlu enum'lardır. Primitive tiplerle doğrudan karşılaştırma ve işlem yapmanızı sağlar ancak tip güvenliğinden ödün verirsiniz.

  • Flag ve IntFlag, bitwise | operatörüyle birleştirilebilen enum'lardır. Dosya izinleri, kullanıcı yetkileri gibi çoklu durum senaryolarında kullanılır.

  • auto() fonksiyonu üyelere otomatik değer atar — Enum'da sıralı integer, Flag'da 2'nin kuvvetleri. _generate_next_value_ ile özelleştirilebilir.

  • @unique decorator'ü aynı değere sahip birden fazla üyeyi yasaklar. Büyük enum'larda yanlışlıkla alias oluşturmayı önlemek için her zaman kullanmanız önerilir.

  • Enum'a metot ekleyerek hem veriyi hem davranışı tek bir yerde toplayabilirsiniz. Pattern matching (match-case) ile birlikte kullanıldığında son derece temiz ve bakımı kolay kod elde edersiniz.


*Bu ders, Python'un enum modülünün temellerinden ileri tekniklerine kadar geniş bir yelpazede bilgi sunmaktadır. Tüm örnekleri kendi bilgisayarınızda deneyerek öğrenmenizi pekiştirmeniz önerilir. Python 3.11+ kullanıyorsanız StrEnum'u, 3.10+ kullanıyorsanız match-case'i mutlaka deneyin.*