Python Sözlüklerinde Anahtar Var mı Kontrolü

Selamlar, bu yazıda Python'da küçük ama sürekli karşımıza çıkan bir konuya bakacağız: bir sözlükte (dict) belli bir anahtarın olup olmadığını nasıl kontrol ederiz? Soru basit görünüyor ama bir-iki tane tuzağı var; özellikle değerin None olduğu durumlarda. Lafı çok uzatmadan başlayayım.

En Pythonic yol: in operatörü

Bir sözlükte anahtarın var olup olmadığını öğrenmenin en okunaklı yolu in operatörü. Eski bir alışkanlıkla has_key gibi bir şey aramayın; o Python 2'de kalmış bir metot, artık yok.

user = {'name': 'Ada', 'age': 30}

if 'name' in user:
    print('isim var')

if 'email' not in user:
    print('email yok')

Burada dikkat: 'name' in user ifadesi sözlüğün anahtarlarına bakar, değerlerine değil. in user.keys() yazmaya gerek yok; doğrudan in user aynı işi yapıyor ve aslında biraz daha hızlı çalışıyor çünkü ekstra bir dict_keys view'ı oluşturmuyoruz.

dict.get ile tek hamlede değer ve kontrol

Anahtarın varlığını test ettikten sonra büyük ihtimalle değerini de okuyacağız. O zaman iki kez sözlüğe bakmak yerine get metodu işimizi tek seferde halleder. Anahtar yoksa varsayılan olarak None döner; istersen kendi varsayılanını da verebilirsin.

config = {'host': 'localhost', 'port': 5432}

host = config.get('host')           # 'localhost'
timeout = config.get('timeout', 30) # 30 (varsayılan)

Burada bir tuzak var: değerin kendisi None ise get ile gerçekten eksik anahtarı ayırt edemezsiniz.

data = {'optional': None}

print(data.get('optional'))  # None
print(data.get('missing'))   # None

İkisi de None döndü. Bu ayrımı yapmamız gerekiyorsa in operatörüne geri dönüyoruz; o sözlükte None değerli bir anahtar varsa bile True döner.

setdefault ile yoksa ekle

Bazen sadece kontrol etmek değil, yoksa ekleyip varsa olanı kullanmak istiyoruz. Klasik kullanım yeri: gruplama (group-by). Mesela bir liste içindeki kelimeleri ilk harflerine göre gruplayalım.

words = ['Ada', 'Ali', 'Bora', 'Burak', 'Can']
groups = {}

for w in words:
    groups.setdefault(w[0], []).append(w)

# groups -> {'A': ['Ada', 'Ali'], 'B': ['Bora', 'Burak'], 'C': ['Can']}

setdefault anahtar yoksa onu boş bir liste ile ekliyor ve o listeyi geri döndürüyor; varsa zaten olan listeyi döndürüyor. Tabii bu işin daha temiz hali collections.defaultdict ama tek seferlik küçük gruplamalar için setdefault yeter.

EAFP stili: try/except KeyError

Python topluluğunda 'önce iznini al' (LBYL) yerine 'önce dene, hata olursa yakala' (EAFP) yaklaşımı yaygındır. Anahtarın çoğu zaman var olduğunu bekliyorsanız bu stil hem daha hızlı hem de daha doğal.

try:
    score = scores['ada']
except KeyError:
    score = 0

Bence bu yaklaşımı her yerde kullanmaya gerek yok; özellikle anahtarın yarı yarıya eksik olduğu durumlarda get daha okunaklı. Ama performans kritik bir döngüde, anahtar çoğu zaman doluyorsa, try/except sürekli if kontrolünden hızlı çıkıyor.

Birden fazla anahtarı tek seferde kontrol

Bir sözlükte birkaç anahtarın hepsinin var olup olmadığını kontrol etmek için tek tek in yazmak gerekmiyor. Set işlemleri imdada yetişiyor.

payload = {'id': 1, 'name': 'Ada', 'email': 'ada@example.com'}
required = {'id', 'name'}

if required <= payload.keys():
    print('zorunlu alanlar tamam')

eksikler = required - payload.keys()
print(eksikler)  # set()

<= burada 'alt küme' demek. dict.keys() zaten küme benzeri bir view döndürdüğü için doğrudan küme operatörlerini uygulayabiliyoruz. Eksikleri görmek için fark almak da çok pratik.

İç içe sözlüklerde güvenli erişim

API cevaplarında genelde iç içe sözlükler oluyor ve her seviyede in kontrolü yapmak yorucu. Küçük bir yardımcı fonksiyon işi kolaylaştırıyor:

def get_nested(data, *keys, default=None):
    cur = data
    for k in keys:
        if not isinstance(cur, dict) or k not in cur:
            return default
        cur = cur[k]
    return cur

resp = {'user': {'profile': {'name': 'Ada'}}}
print(get_nested(resp, 'user', 'profile', 'name'))     # 'Ada'
print(get_nested(resp, 'user', 'address', 'city', default='-'))  # '-'

Kapanış

Özetle: var-yok kontrolü için in, değer de lazımsa get, yoksa ekle istiyorsan setdefault, çoğu zaman dolu olduğunu biliyorsan try/except. Bana sorarsanız kodun çoğunda in ile get ikilisi yetiyor; diğerleri ise doğru senaryoda kullanıldığında kodu kısaltıyor. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.