Python'da enum modülü, sınırlı sayıda seçenekten birini alabilen değerler için adlandırılmış sabitler tanımlamanızı sağlar. class Durum(Enum): altına KARGODA = 3 gibi satırlar yazdığınızda her satır bir üye (member) olur; üyenin name ve value öznitelikleri vardır, Durum(3) ile değerden, Durum['KARGODA'] ile addan üyeye ulaşılır. Tam sayı gibi davranması gereken sabitler için IntEnum, metin gibi davrananlar için StrEnum, bit bayrakları için Flag ve IntFlag kullanılır; değerleri elle yazmak istemezseniz auto() devreye girer.

Bu yazıda Enum üyelerinin nasıl oluşturulup arandığını, takma adları (alias) ve @unique'i, karşılaştırma kurallarını, IntEnum ile StrEnum'un farkını, auto() değerlerini, Flag birleşimlerini, Enum'a metot ve öznitelik eklemeyi, _missing_ ile esnek aramayı ve fonksiyonel API'yi Python 3.15.2 ile çalıştırılmış örneklerle anlatıyoruz. Sınıf temelleri için class ve nesne yazısına, serinin tamamı için Python öğrenme rehberine göz atabilirsiniz.

Neden düz sabitler yerine Enum?

Neden düz sabitler yerine Enum?

KARGODA = 3 gibi modül düzeyinde bir sabit de iş görür, ama hata ayıklarken ekranda yalnızca 3 görürsünüz ve o sayının hangi kümeye ait olduğu belli olmaz. Enum HOWTO belgesi Enum'ları global değişkenlere benzetir, ancak daha kullanışlı bir repr(), gruplama ve tip güvenliği sunduklarını vurgular. Bir sipariş durumu yanlışlıkla bir ödeme durumuyla karşılaştırılamaz, bir fonksiyon yalnızca Durum üyelerini kabul edecek şekilde denetlenebilir ve geçerli seçeneklerin tam listesi her zaman sınıfın üzerinden okunabilir.

Adlandırma için belgelerin güçlü önerisi üyeleri BÜYÜK_HARFLE yazmaktır: üyeler sabit oldukları için hem okunabilirlik artar hem de karışım (mixin) sınıflarının metot adlarıyla çakışma riski azalır. Bu yazıdaki örneklerde üye adlarını Türkçe ama ASCII harflerle (YUKSEK, GUNEY) yazdık.

Enum tanımlama, arama ve yineleme

python
from enum import Enum

class Durum(Enum):
    BEKLIYOR = 1
    HAZIRLANIYOR = 2
    KARGODA = 3
    TESLIM = 4

print(Durum.KARGODA)
print(repr(Durum.KARGODA))
print(Durum.KARGODA.name, Durum.KARGODA.value)
print(Durum(2), Durum['TESLIM'])
print(type(Durum.TESLIM), isinstance(Durum.TESLIM, Durum))
print(len(Durum), [d.name for d in Durum])
print(Durum.TESLIM in Durum, 4 in Durum, 9 in Durum)
# çıktı: Durum.KARGODA
# çıktı: <Durum.KARGODA: 3>
# çıktı: KARGODA 3
# çıktı: Durum.HAZIRLANIYOR Durum.TESLIM
# çıktı: <enum 'Durum'> True
# çıktı: 4 ['BEKLIYOR', 'HAZIRLANIYOR', 'KARGODA', 'TESLIM']
# çıktı: True True False

Çıktıları sırayla okuyalım. str() (ve print()) yalnızca sınıf ve üye adını, repr() ise değeri de gösterir: <Durum.KARGODA: 3>. Üyenin tipi Enum sınıfının kendisidir. Çağrı söz dizimi değere göre, köşeli parantez ada göre arar. Sınıf üzerinde yineleme üyeleri tanım sırasıyla verir ve len() üye sayısını döndürür. in işleci hem üyeleri hem değerleri kabul eder; belgelere göre Python 3.12 öncesinde üye olmayan bir değerle in denetimi TypeError veriyordu, artık True ya da False döndürüyor.

Geçersiz bir değer ya da ad aradığınızda alacağınız hatalar ve üyeleri değiştirme girişiminin sonucu:

python
from enum import Enum

class Renk(Enum):
    KIRMIZI = 1
    YESIL = 2

try:
    Renk(7)
except ValueError as e:
    print(e)
try:
    Renk['MAVI']
except KeyError as e:
    print('KeyError:', e)
try:
    Renk.KIRMIZI = 5
except AttributeError as e:
    print(e)
# çıktı: 7 is not a valid Renk
# çıktı: KeyError: 'MAVI'
# çıktı: cannot reassign member 'KIRMIZI'

Kullanıcıdan ya da bir dosyadan gelen değeri Enum'a çevirirken bu ValueError'ı yakalamak, geçersiz girdiyi erken reddetmenin temiz bir yoludur. Hata yakalama kalıpları için try-except yazısına bakabilirsiniz.

Takma adlar ve @unique

Aynı adı iki kez tanımlamak hata verir, ama iki farklı adın aynı değeri taşıması serbesttir. Bu durumda ikinci ad yeni bir üye değil, ilk üyenin takma adıdır (alias): ada göre arandığında da değere göre arandığında da ilk üye döner. Yinelemede takma adlar görünmez; __members__ eşlemesi ise takma adlar dahil bütün adları içerir:

python
from enum import Enum, unique

class Sekil(Enum):
    KARE = 2
    BAKLAVA = 1
    DAIRE = 3
    KARE_TAKMA_AD = 2           # aynı değer: takma ad (alias)

print(Sekil.KARE_TAKMA_AD, Sekil(2))
print(list(Sekil))
print(list(Sekil.__members__))
print([ad for ad, uye in Sekil.__members__.items() if uye.name != ad])

try:
    @unique
    class Hata(Enum):
        BIR = 1
        IKI = 2
        UC = 3
        DORT = 3
except ValueError as e:
    print(e)
# çıktı: Sekil.KARE Sekil.KARE
# çıktı: [<Sekil.KARE: 2>, <Sekil.BAKLAVA: 1>, <Sekil.DAIRE: 3>]
# çıktı: ['KARE', 'BAKLAVA', 'DAIRE', 'KARE_TAKMA_AD']
# çıktı: ['KARE_TAKMA_AD']
# çıktı: duplicate values found in <enum 'Hata'>: DORT -> UC

Takma ad istemiyorsanız sınıfı @unique ile işaretleyin; tekrar eden bir değer bulunduğunda sınıf oluşturulmaz ve hangi adın hangisinin kopyası olduğu mesajda yazar. Daha ayrıntılı kısıtlar için @verify dekoratörü ve EnumCheck seçenekleri (UNIQUE, CONTINUOUS, NAMED_FLAGS) vardır. Python 3.13 ile çalışma anında takma ad eklemek için _add_alias_() ve _add_value_alias_() metotları geldi.

Karşılaştırma: Enum ve IntEnum farkı

Enum üyeleri kimlikle karşılaştırılır; her üye tekil bir nesnedir, bu yüzden is güvenle kullanılabilir. Eşitlik (==) tanımlıdır ama Enum olmayan değerlerle karşılaştırma her zaman False döner. Sıralama karşılaştırması ise desteklenmez. IntEnum bilerek farklı tasarlanmıştır: üyeleri int'in alt sınıfıdır, tam sayılarla karşılaştırılabilir, sıralanabilir ve tam sayı beklenen her yerde, örneğin liste indeksinde, kullanılabilir:

python
from enum import Enum, IntEnum

class Renk(Enum):
    KIRMIZI = 1
    MAVI = 3

class Oncelik(IntEnum):
    DUSUK = 1
    ORTA = 2
    YUKSEK = 3

print(Renk.KIRMIZI is Renk.KIRMIZI, Renk.KIRMIZI == 1, Renk.KIRMIZI.value == 1)
try:
    Renk.KIRMIZI < Renk.MAVI
except TypeError as e:
    print(e)
print(Oncelik.YUKSEK > Oncelik.DUSUK, Oncelik.ORTA == 2, Oncelik.ORTA + 1)
print(sorted([Oncelik.YUKSEK, Oncelik.DUSUK]))
print(str(Oncelik.ORTA), f'{Oncelik.ORTA}', ['a', 'b', 'c'][Oncelik.DUSUK])
# çıktı: True False True
# çıktı: '<' not supported between instances of 'Renk' and 'Renk'
# çıktı: True True 3
# çıktı: [<Oncelik.DUSUK: 1>, <Oncelik.YUKSEK: 3>]
# çıktı: 2 2 b

Oncelik.ORTA + 1 sonucunun düz 3 olduğuna dikkat edin: belgelere göre bir IntEnum üyesiyle tam sayı işlemi yapıldığında sonuç Enum niteliğini kaybeder. Python 3.11'den beri IntEnum'un str() sonucu da int'inki gibidir ('2'); amaç, var olan tam sayı sabitlerinin yerine sorunsuz geçebilmesidir. Genel öneri: değerin sayı olarak kullanılması gerçekten gerekmiyorsa düz Enum tercih edin, çünkü yanlışlıkla sayıyla karşılaştırma gibi hataları yakalar.

auto() ve StrEnum

Değerin ne olduğu önemli değilse auto() uygun değeri sizin yerinize seçer. Düz Enum ve IntEnum'da 1'den başlayıp artan tam sayılar, Flag ve IntFlag'de 1'den başlayan ikinin kuvvetleri, StrEnum'da ise üye adının küçük harfli hâli üretilir. Python 3.13'ten beri sonraki değer, son görülen değere göre değil, görülen en yüksek değere göre hesaplanıyor. Değer üretimini _generate_next_value_ statik metoduyla özelleştirebilirsiniz; bu metot üyelerden önce tanımlanmalıdır.

python
from enum import Enum, StrEnum, auto
import json

class Yon(Enum):
    KUZEY = auto()
    GUNEY = auto()
    DOGU = auto()

class Ortam(StrEnum):
    GELISTIRME = auto()
    CANLI = auto()

print([y.value for y in Yon])
print(repr(Ortam.CANLI), str(Ortam.CANLI))
print(Ortam.CANLI == 'canli', Ortam('gelistirme'))
print(json.dumps({'ortam': Ortam.CANLI}))
print(f'/etc/uygulama/{Ortam.CANLI}.conf')
# çıktı: [1, 2, 3]
# çıktı: <Ortam.CANLI: 'canli'> canli
# çıktı: True gelistirme
# çıktı: {"ortam": "canli"}
# çıktı: /etc/uygulama/canli.conf

StrEnum üyeleri str'nin alt sınıfı olduğu için metinle karşılaştırılabilir, f-string içinde doğrudan değerleriyle yazılır ve json.dumps() tarafından düz metin olarak serileştirilir. Yapılandırma dosyalarındaki ortam adları, HTTP yöntemleri ya da dosya uzantıları için pratik bir seçimdir. Belgelerin uyarısı: standart kütüphanede tam olarak str tipi bekleyen (type(x) == str) yerler vardır, oralarda str(Uye) ile çevirmeniz gerekir. JSON ayrıntıları için json modülü yazısına bakabilirsiniz.

Flag ve IntFlag: bit bayrakları

Flag ve IntFlag: bit bayrakları: Enum, IntEnum, StrEnum, Flag

Flag üyeleri &, |, ^ ve ~ işleçleriyle birleştirilebilir ve sonuç yine aynı Flag tipinden olur. Değerler ikinin kuvveti olmalıdır; auto() bunu kendiliğinden sağlar. Birleşimin içinde bir bayrağın olup olmadığı in ile denetlenir, birleşim üzerinde yinelemek içerdiği tekil bayrakları verir. IntFlag ayrıca int gibi davranır; Unix dosya izinleri gibi sayısal bayraklar için uygundur:

python
from enum import IntFlag, Flag, auto

class Izin(IntFlag):
    R = 4
    W = 2
    X = 1

rw = Izin.R | Izin.W
print(repr(rw), int(rw))
print(Izin.R in rw, Izin.X in rw)
print(list(rw))
print(repr(Izin.R & Izin.X), bool(Izin.R & Izin.X))
print(repr(Izin(7)), str(Izin(7)), Izin.R + 1)

class Gun(Flag):
    PZT = auto()
    SAL = auto()
    CAR = auto()
    CMT = auto()
    PAZ = auto()
    HAFTASONU = CMT | PAZ

print([g.value for g in Gun])
print(Gun.CMT in Gun.HAFTASONU, repr(Gun.PZT | Gun.CAR))
# çıktı: <Izin.R|W: 6> 6
# çıktı: True False
# çıktı: [<Izin.R: 4>, <Izin.W: 2>]
# çıktı: <Izin: 0> False
# çıktı: <Izin.R|W|X: 7> 7 5
# çıktı: [1, 2, 4, 8, 16]
# çıktı: True <Gun.PZT|CAR: 5>

Hiçbir bayrağın seçili olmadığı birleşim (<Izin: 0>) yanlış (False) sayılır; düz Enum üyeleri ise her zaman doğrudur. Sınıf içinde birleşimlere ad verebilirsiniz (HAFTASONU = CMT | PAZ); bu adlar takma ad sayılır ve yinelemede görünmez. IntFlag'in str() sonucunun da sayı olduğuna, toplama gibi bit dışı işlemlerin sonucun Flag niteliğini kaldırdığına dikkat edin. Geçersiz değerlerle ne yapılacağı FlagBoundary ayarına (STRICT, CONFORM, EJECT, KEEP) bağlıdır.

TürTabanSayı/metinle ==Sıralamaauto() değeri
EnumEnumHayır (her zaman False)Yok1, 2, 3...
IntEnumint ve EnumEvetVar1, 2, 3...
StrEnumstr ve EnumEvetMetin sıralamasıKüçük harfli üye adı
FlagEnumHayırYok1, 2, 4, 8...
IntFlagint ve FlagEvetVar1, 2, 4, 8...

Enum'a metot, öznitelik ve özel davranış eklemek

Python Enum'ları yalnızca ad-değer çiftleri değildir; sıradan sınıflar gibi metot, classmethod ve property taşıyabilir. Metot içinde self üyenin kendisidir. Üye değeri bir demet olduğunda ve sınıf __init__ tanımladığında, demetin elemanları ayrı argümanlar olarak __init__'e iletilir. Belgelerdeki gezegen örneğinin kargo seçeneklerine uyarlanmış hâli:

python
from enum import Enum

class Kargo(Enum):
    STANDART = (3, 49.90)
    HIZLI = (1, 89.90)

    def __init__(self, gun, ucret):
        self.gun = gun
        self.ucret = ucret

    @property
    def aciklama(self):
        return f'{self.name.title()}: {self.gun} gün, {self.ucret} TL'

    @classmethod
    def en_ucuz(cls):
        return min(cls, key=lambda k: k.ucret)

print(Kargo.HIZLI.value)
print(Kargo.HIZLI.aciklama)
print(Kargo.en_ucuz())
print(Kargo((1, 89.90)))
# çıktı: (1, 89.9)
# çıktı: Hizli: 1 gün, 89.9 TL
# çıktı: Kargo.STANDART
# çıktı: Kargo.HIZLI

Belgelere göre üyenin değerini değiştirmek istiyorsanız (örneğin demetin yalnızca ilk elemanını değer yapmak) __new__ gerekir ve içinde _value_ atanır; diğer her türlü özelleştirme için __init__ tercih edilir. Hangi adların üye olacağı da kurallıdır: tek alt çizgiyle başlayıp biten adlar Enum'a ayrılmıştır, özel metotlar, betimleyiciler (metotlar dahil) ve _ignore_ listesindeki adlar üye olmaz, geri kalan her atama üye olur. Python 3.11 ile _Ozel__ad biçimindeki karıştırılmış adlar da üye sayılmıyor.

_missing_ ile esnek arama

Değere göre arama bulunamadığında Python _missing_ sınıf metodunu çağırır. Varsayılanı hiçbir şey yapmaz, ama onu geçersiz kılarak büyük/küçük harf duyarsız arama ya da eski değerleri yeni üyelere eşleme gibi kurallar yazabilirsiniz. Metot bir üye döndürürse arama başarılı sayılır, None döndürürse olağan ValueError oluşur:

python
from enum import StrEnum, auto

class Seviye(StrEnum):
    DEBUG = auto()
    INFO = auto()

    @classmethod
    def _missing_(cls, deger):
        deger = deger.lower()
        for uye in cls:
            if uye.value == deger:
                return uye
        return None

print(Seviye('INFO'), repr(Seviye('Debug')))
try:
    Seviye('trace')
except ValueError as e:
    print(e)
# çıktı: info <Seviye.DEBUG: 'debug'>
# çıktı: 'trace' is not a valid Seviye

Fonksiyonel API, alt sınıf kuralı ve match

Enum sınıfı çağrılabilir: Enum('Hayvan', 'KEDI KOPEK KUS') aynı adlı sınıf söz dizimiyle eşdeğer bir Enum oluşturur. İkinci argüman boşlukla ya da virgülle ayrılmış adlar, ad listesi, (ad, değer) çiftleri ya da bir sözlük olabilir; değer verilmezse 1'den başlanır. Belgeler 0 yerine 1'den başlanmasının nedenini de açıklar: 0 mantıksal olarak yanlıştır, Enum üyeleri ise varsayılan olarak doğru sayılır. Fonksiyonel API ile oluşturulan Enum'ları pickle ile serileştirmek için module=__name__ vermeniz önerilir.

python
from enum import Enum

Hayvan = Enum('Hayvan', 'KEDI KOPEK KUS')
print(list(Hayvan))

class Renk(Enum):
    KIRMIZI = 1

try:
    class DahaFazlaRenk(Renk):
        PEMBE = 17
except TypeError as e:
    print(e)

class Davranis(Enum):           # üyesiz enum: ortak davranış
    def etiket(self):
        return self.name.lower()

class Boyut(Davranis):
    KUCUK = 1
    BUYUK = 2

print(Boyut.BUYUK.etiket())

def ucret(boyut):
    match boyut:
        case Boyut.KUCUK:
            return 10
        case Boyut.BUYUK:
            return 25

print(ucret(Boyut.BUYUK))
# çıktı: [<Hayvan.KEDI: 1>, <Hayvan.KOPEK: 2>, <Hayvan.KUS: 3>]
# çıktı: <enum 'DahaFazlaRenk'> cannot extend <enum 'Renk'>
# çıktı: buyuk
# çıktı: 25

Üyesi olan bir Enum'dan türetme yapılamaz; buna izin verilseydi tiplerle örnekler arasındaki bazı temel değişmezler bozulurdu. Ortak davranışı paylaşmanın yolu, üyesiz bir taban Enum'da metotları tanımlayıp üyeleri alt sınıfa koymaktır. Enum üyeleri match deyiminde noktalı adlarla (case Boyut.KUCUK:) değer deseni olarak kullanılabilir; ayrıntılar match-case yazısında. Dataclass ile Enum'un birlikte kullanımı 3.12'de desteklendi; değer olarak dataclass kullanmak için dataclasses yazısına bakabilirsiniz.

İki pratik ayrıntı daha. Enum üyeleri pickle ile serileştirilip geri yüklendiğinde aynı üye nesnesine döner; ancak bunun için Enum'un bir modülün en üst düzeyinde tanımlı olması gerekir, çünkü geri yükleme sınıfın o modülden içe aktarılabilmesine dayanır. Varsayılan serileştirme değere göredir. İkincisi doğruluk değeri: düz Enum üyeleri, değerleri 0 bile olsa her zaman doğru sayılır. Üyenin doğruluğunun değerine bağlı olmasını istiyorsanız belgelerin önerdiği gibi sınıfa def __bool__(self): return bool(self.value) ekleyin. int ya da str ile karışan Enum'lar ise karıştıkları tipin kurallarına uyar.

Sık yapılan hatalar

Sık yapılan hatalar: Üyeyi değeriyle karşılaştırmak, Enum değerini sıralamaya çalışmak, Üyeyi değiştirmeye çalışmak, Üyeli Enum'dan türetmek
  • Üyeyi değeriyle karşılaştırmak: durum == 3 düz Enum'da her zaman False döner. durum is Durum.KARGODA ya da durum.value == 3 yazın.
  • Enum değerini sıralamaya çalışmak: Düz Enum'da < desteklenmez. Sıra gerekiyorsa IntEnum kullanın ya da sorted(..., key=lambda u: u.value) ile değere göre sıralayın.
  • Üyeyi değiştirmeye çalışmak: Renk.KIRMIZI = 5 AttributeError verir; üyeler sabittir.
  • Üyeli Enum'dan türetmek: TypeError verir; ortak davranışı üyesiz taban Enum'a koyun.
  • Flag değerlerini elle yanlış seçmek: 1, 2, 3 gibi ikinin kuvveti olmayan değerler bayrakları birbirine karıştırır; auto() kullanın.
  • Modül yeniden yüklemesi: Belgeler, Enum içeren bir modül yeniden yüklendiğinde yeni üyelerin eskilerle aynı nesne ya da eşit olmayabileceğini uyarır.

Sık Sorulan Sorular

Enum değerini veritabanına ya da JSON'a nasıl yazarım?

Düz Enum'da uye.value ya da uye.name değerini yazıp okurken Durum(deger) veya Durum[ad] ile geri çevirin. StrEnum ve IntEnum üyeleri zaten metin ve sayı oldukları için doğrudan serileştirilebilir. Veritabanı tarafı için sqlite3 yazısına bakabilirsiniz.

Enum üyelerini liste olarak nasıl alırım?

list(Durum) takma adlar hariç üyeleri tanım sırasıyla verir. Adlar için [u.name for u in Durum], takma adlar dahil her şey için Durum.__members__ kullanılır.

auto() ile verilen değerler program her çalıştığında aynı mı?

Evet. Değerler tanım sırasına göre belirlenir ve _generate_next_value_ değiştirilmedikçe aynı kalır. Yine de değer kalıcı olarak saklanacaksa (veritabanı, dosya) sınıfın ortasına yeni üye eklemenin sonraki değerleri kaydıracağını unutmayın; böyle durumlarda değerleri açıkça yazmak daha güvenlidir.

Enum ile sabit bir sözlük arasındaki fark nedir?

Sözlük değiştirilebilir ve anahtarları sıradan metinlerdir. Enum üyeleri ise değiştirilemez, tekil nesnelerdir, kendi tiplerine sahiptir ve metot taşıyabilir. Seçenek kümesi sabitse Enum, çalışma anında değişen eşlemeler için sözlük daha uygundur.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Python öğrenme rehberi · Class ve nesne · dataclasses · match-case · json modülü