Custom Exceptions ve Best Practices
Önceki derste Python'ın yerleşik exception'larını öğrendik. Ama gerçek projelerde bazen ValueError ya da TypeError yetmez — kendi anlamlı hata türlerini oluşturman gerekir. Bu derste custom exception'lar, exception chaining ve hata yönetimi best practice'lerini keşfedeceğiz.
Neden Custom Exception?
Python'ın yerleşik exception'ları genel amaçlıdır. Ama diyelim ki bir bankacılık uygulaması yazıyorsun:
def para_cek(hesap, miktar):
if miktar > hesap.bakiye:
raise ValueError("Yetersiz bakiye")Bu çalışır ama sorunlu. ValueError çok genel — kodun herhangi bir yerinde oluşabilir. Bunu yakalayan kişi "Bu ValueError bakiye yetersizliğinden mi, yoksa geçersiz bir girişten mi?" diye düşünmek zorunda kalır.
🏷️ Etiketli Kutu Analojisi
Custom exception'ları etiketli kutular gibi düşün. Bir depoda her şeyi aynı kahverengi kutuya koyarsan, aradığını bulmak imkansızlaşır. Ama kutuların üzerinde "Elektronik", "Mutfak Eşyası", "Kırtasiye" yazıyorsa? İstediğini anında bulursun.
ValueError etiketlenmemiş bir kutu. InsufficientBalanceError ise üzerinde "Yetersiz Bakiye" yazan, tam olarak ne içerdiğini bildiğin bir kutu.
Custom exception'ların avantajları:
Okunabilirlik: Kod okuyan kişi hatanın ne olduğunu anında anlar
Spesifik yakalama: Sadece ilgilendiğin hataları yakalarsın
Ek bilgi: Hataya özel veri ekleyebilirsin (miktar, hesap no, tarih...)
API tasarımı: Kütüphane/modül kullanıcılarına net hata bilgisi verirsin
Custom Exception Tanımlama
Custom exception tanımlamak çok basit — Exception sınıfından türetmek yeterli:
class YetersizBakiyeError(Exception):
"""Hesapta yeterli bakiye olmadığında fırlatılır."""
pass
class GecersizTutarError(Exception):
"""Geçersiz para tutarı girildiğinde fırlatılır."""
passBu kadar! Şimdi bu exception'ları kullanabiliriz:
def para_cek(hesap_bakiye, miktar):
if miktar <= 0:
raise GecersizTutarError(f"Tutar pozitif olmalı: {miktar}")
if miktar > hesap_bakiye:
raise YetersizBakiyeError(
f"{miktar} TL çekilemez, bakiye: {hesap_bakiye} TL"
)
return hesap_bakiye - miktar
# Kullanım
try:
yeni_bakiye = para_cek(1000, 1500)
except YetersizBakiyeError as e:
print(f"Bakiye yetersiz: {e}")
except GecersizTutarError as e:
print(f"Geçersiz tutar: {e}")Artık her hata türünü ayrı ayrı yakalayıp farklı aksiyonlar alabilirsin.
İsimlendirme Kuralı
Python'da exception isimleri her zaman `Error` ile biter (convention):
# DOĞRU
class DatabaseConnectionError(Exception): ...
class InvalidEmailError(Exception): ...
class AuthenticationError(Exception): ...
# YANLIŞ (çalışır ama convention dışı)
class DatabaseProblem(Exception): ... # "Error" yok
class invalid_email(Exception): ... # snake_case değil, PascalCase olmalıException'a Mesaj ve Ekstra Veri Ekleme
Basit bir pass ile tanımlanan exception yeterli olabilir ama çoğu zaman ekstra veri taşımak istersin:
class YetersizBakiyeError(Exception):
def __init__(self, hesap_no, bakiye, istenen_miktar):
self.hesap_no = hesap_no
self.bakiye = bakiye
self.istenen_miktar = istenen_miktar
self.eksik = istenen_miktar - bakiye
mesaj = (
f"Hesap {hesap_no}: {istenen_miktar} TL çekilemez. "
f"Mevcut bakiye: {bakiye} TL, "
f"Eksik: {self.eksik} TL"
)
super().__init__(mesaj)Şimdi bu exception'ı yakaladığında ekstra bilgilere erişebilirsin:
try:
raise YetersizBakiyeError("TR001", 500, 750)
except YetersizBakiyeError as e:
print(e)
# Hesap TR001: 750 TL çekilemez. Mevcut bakiye: 500 TL, Eksik: 250 TL
print(f"Hesap: {e.hesap_no}")
print(f"Bakiye: {e.bakiye}")
print(f"İstenen: {e.istenen_miktar}")
print(f"Eksik tutar: {e.eksik}")super().__init__(mesaj) çağrısı önemli — bu sayede str(e) dediğinde güzel bir mesaj alırsın.
Exception Hiyerarşisi Oluşturma
Büyük projelerde exception'larını bir hiyerarşi halinde organize etmek iyi bir pratik:
# Temel exception — tüm proje exception'larının atası
class BankaHatasi(Exception):
"""Bankacılık uygulamasının temel exception sınıfı."""
pass
# Alt exception'lar
class HesapHatasi(BankaHatasi):
"""Hesapla ilgili hatalar."""
pass
class YetersizBakiyeError(HesapHatasi):
"""Yetersiz bakiye."""
pass
class HesapDondurulmusError(HesapHatasi):
"""Dondurulmuş hesap."""
pass
class KimlikDogrulamaHatasi(BankaHatasi):
"""Kimlik doğrulama hataları."""
pass
class GecersizSifreError(KimlikDogrulamaHatasi):
"""Geçersiz şifre."""
passBu hiyerarşinin güzelliği: farklı seviyelerde yakalama yapabilirsin.
try:
banka_islemi()
except YetersizBakiyeError:
# Sadece bakiye yetersizliği
print("Bakiye yetersiz")
except HesapHatasi:
# Tüm hesap hataları (bakiye, dondurulmuş vs.)
print("Hesap hatası")
except BankaHatasi:
# Tüm banka hataları
print("Banka sistemi hatası")Bu yapı kütüphane yazarken özellikle değerli. Kullanıcılar istedikleri detay seviyesinde hata yakalayabilir.
Exception Chaining: raise ... from ...
Bazen bir exception yakalar ve farklı bir exception fırlatırsın. Bu durumda orijinal hatanın kaybolmaması önemli. Python 3'te raise ... from ... syntax'ı bunu sağlar.
class VeriIslemeHatasi(Exception):
"""Veri işleme sırasında oluşan hata."""
pass
def json_oku(dosya_yolu):
import json
try:
with open(dosya_yolu) as f:
return json.load(f)
except FileNotFoundError as e:
raise VeriIslemeHatasi(
f"Yapılandırma dosyası bulunamadı: {dosya_yolu}"
) from e
except json.JSONDecodeError as e:
raise VeriIslemeHatasi(
f"Geçersiz JSON formatı: {dosya_yolu}"
) from efrom e ifadesi, yeni exception'ın orijinal hataya bağlı olduğunu belirtir. Traceback'te her iki hata da görünür:
FileNotFoundError: [Errno 2] No such file or directory: 'config.json'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
...
VeriIslemeHatasi: Yapılandırma dosyası bulunamadı: config.jsonfrom None ile Zinciri Kırma
Bazen orijinal hatayı gizlemek istersin — özellikle iç detayları dışarıya sızdırmak istemediğinde:
def kullanici_bul(kullanici_id):
try:
# İç implementasyon detayı — kullanıcı bilmemeli
return veritabani.sorgula(f"SELECT * FROM users WHERE id={kullanici_id}")
except DatabaseInternalError:
raise KullaniciBulunamadiError(
f"Kullanıcı bulunamadı: {kullanici_id}"
) from None # Orijinal hatayı gizlefrom None kullanıldığında traceback'te sadece yeni exception görünür. Veritabanı detayları (tablo adı, SQL sorgusu) dışarıya sızmaz.
Implicit Chaining
from kullanmasan bile, bir except bloğu içinde yeni exception fırlatırsan Python otomatik olarak zincirler:
try:
int("abc")
except ValueError:
raise RuntimeError("Dönüştürme başarısız")
# "During handling of the above exception, another exception occurred:"Ama from kullanmak niyetini açıkça belirtir: "Bu hata, şu hatadan kaynaklanıyor."
Exception Best Practices
Yılların deneyimiyle oluşmuş en iyi pratikleri inceleyelim.
1. Spesifik Exception Yakala
# KÖTÜ
try:
veri = isle(girdi)
except Exception:
print("Hata oluştu")
# İYİ
try:
veri = isle(girdi)
except ValueError:
print("Geçersiz değer")
except KeyError:
print("Anahtar bulunamadı")Ne kadar spesifik yakalarsan, hataları o kadar iyi ele alırsın. Genel exception yakalamak bug'ları gizler.
2. try Bloğunu Kısa Tut
# KÖTÜ — Çok fazla kod try içinde
try:
dosya = open("veri.json")
icerik = dosya.read()
dosya.close()
veri = json.loads(icerik)
sonuc = hesapla(veri)
raporla(sonuc)
bildirim_gonder()
except Exception as e:
print(f"Hata: {e}")
# İYİ — Sadece riskli kısmı koru
try:
with open("veri.json") as f:
veri = json.load(f)
except FileNotFoundError:
print("Dosya bulunamadı")
veri = varsayilan_veri()
except json.JSONDecodeError:
print("JSON formatı hatalı")
veri = varsayilan_veri()
# Geri kalan try dışında
sonuc = hesapla(veri)
raporla(sonuc)3. EAFP vs LBYL
Python'da iki farklı hata yönetim stili var:
LBYL (Look Before You Leap — "Atlamadan Önce Bak"):
# LBYL stili — önce kontrol et
if "anahtar" in sozluk:
deger = sozluk["anahtar"]
else:
deger = "varsayilan"
if os.path.exists(dosya_yolu):
with open(dosya_yolu) as f:
icerik = f.read()EAFP (Easier to Ask Forgiveness than Permission — "İzin Almaktansa Af Dilemek Kolay"):
# EAFP stili — dene, hata olursa yakala
try:
deger = sozluk["anahtar"]
except KeyError:
deger = "varsayilan"
try:
with open(dosya_yolu) as f:
icerik = f.read()
except FileNotFoundError:
icerik = ""Python topluluğu genellikle EAFP'yi tercih eder. Neden?
Race condition yok: LBYL'de kontrol ile kullanım arasında durum değişebilir (dosya silinebilir)
Pythonic: Python'ın doğasına daha uygun
Performans: Hata nadir oluşuyorsa EAFP daha hızlı (kontrol maliyeti yok)
Duck typing: "Ördek gibi yürüyorsa, ördek gibi vaklıyorsa, ördektir"
Ama LBYL'in de yeri var — özellikle kontrolün basit ve hatanın sık oluşacağı durumlarda:
# LBYL burada daha mantıklı
if kullanici_yas >= 18:
alkol_sat()
else:
print("Yaş sınırı!")💡 İpucu: Genel kural: Hata nadir oluşuyorsa EAFP kullan, sık oluşuyorsa LBYL kullan. Python'da EAFP daha yaygın ve tercih edilen stildir, ama fanatik olma — duruma göre en okunaklı olanı seç.
4. Hatayı Doğru Seviyede Yakala
Bir fonksiyon zincirinde hatayı en anlamlı yerde yakala:
# Alt seviye fonksiyon — hatayı fırlatsın
def dosya_oku(yol):
with open(yol) as f:
return f.read()
# Orta seviye — dönüştürme hatalarını ele alsın
def veri_yukle(yol):
icerik = dosya_oku(yol) # FileNotFoundError yukarı çıksın
try:
return json.loads(icerik)
except json.JSONDecodeError as e:
raise VeriFormatiHatasi(f"JSON hatası: {yol}") from e
# Üst seviye — kullanıcıyla etkileşim
def ana_islem():
try:
veri = veri_yukle("config.json")
except FileNotFoundError:
print("Yapılandırma dosyası bulunamadı!")
except VeriFormatiHatasi as e:
print(f"Yapılandırma hatası: {e}")logging ile Hata Kaydetme
print() ile hata mesajı yazmak geliştirme sırasında işe yarar ama production'da yetersiz kalır. Python'ın logging modülü profesyonel hata kaydetme için kullanılır:
import logging
# Logger ayarla
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
filename="uygulama.log"
)
logger = logging.getLogger(__name__)
def kullanici_giris(kullanici_adi, sifre):
try:
kullanici = veritabani_bul(kullanici_adi)
if not sifre_dogrula(kullanici, sifre):
logger.warning(f"Başarısız giriş denemesi: {kullanici_adi}")
raise KimlikDogrulamaHatasi("Geçersiz şifre")
logger.info(f"Başarılı giriş: {kullanici_adi}")
return kullanici
except VeriTabaniHatasi as e:
logger.error(f"Veritabanı hatası: {e}", exc_info=True)
raiseLogging Seviyeleri
logger.debug("Detay bilgisi — geliştirme sırasında")
logger.info("Bilgi — normal işlem akışı")
logger.warning("Uyarı — potansiyel sorun")
logger.error("Hata — bir şey yanlış gitti")
logger.critical("Kritik — sistem çökebilir")exc_info ile Traceback Loglama
try:
riskli_islem()
except Exception as e:
# exc_info=True traceback'i de loglar
logger.error("İşlem başarısız", exc_info=True)Bu log dosyasında tam traceback'i gösterir — debug için hayat kurtarır.
# Alternatif: logger.exception() — otomatik exc_info=True
try:
riskli_islem()
except Exception as e:
logger.exception("İşlem başarısız") # Traceback otomatik eklenirtraceback Modülü
traceback modülü exception bilgilerini programatik olarak işlemeni sağlar:
import traceback
def hatali_fonksiyon():
return 1 / 0
def cagiran_fonksiyon():
hatali_fonksiyon()
try:
cagiran_fonksiyon()
except ZeroDivisionError:
# Traceback'i string olarak al
tb_str = traceback.format_exc()
print("Yakalanan hata:")
print(tb_str)Çıktı:
Yakalanan hata:
Traceback (most recent call last):
File "main.py", line 9, in <module>
cagiran_fonksiyon()
File "main.py", line 6, in cagiran_fonksiyon
hatali_fonksiyon()
File "main.py", line 3, in hatali_fonksiyon
return 1 / 0
ZeroDivisionError: division by zerotraceback ile Dosyaya Yazma
import traceback
try:
riskli_islem()
except Exception:
with open("hata_log.txt", "a") as f:
traceback.print_exc(file=f)Kullanışlı traceback Fonksiyonları
import traceback
import sys
try:
1 / 0
except ZeroDivisionError:
# Mevcut exception bilgisini al
exc_type, exc_value, exc_tb = sys.exc_info()
# Sadece stack frame'leri al
frames = traceback.extract_tb(exc_tb)
for frame in frames:
print(f"Dosya: {frame.filename}, Satır: {frame.lineno}, "
f"Fonksiyon: {frame.name}")
# Formatlanmış traceback string listesi
lines = traceback.format_exception(exc_type, exc_value, exc_tb)
print("".join(lines))Production'da genellikle logging + exc_info=True kullanırsın. traceback modülü daha düşük seviyeli kontrol gerektiğinde işe yarar.
warnings Modülü
Bazen bir hata fırlatmak yerine sadece uyarı vermek istersin. warnings modülü tam olarak bunu yapar — program çalışmaya devam eder ama kullanıcıyı bilgilendirir.
import warnings
def eski_fonksiyon():
warnings.warn(
"eski_fonksiyon() kullanımdan kaldırılacak, "
"yeni_fonksiyon() kullanın.",
DeprecationWarning,
stacklevel=2
)
# Eski implementasyon...
return "eski sonuç"
# Kullanım
sonuc = eski_fonksiyon()
# UserWarning: eski_fonksiyon() kullanımdan kaldırılacak...
print(sonuc) # "eski sonuç" — program devam ederYaygın Warning Türleri
import warnings
# DeprecationWarning — Kullanımdan kaldırılacak özellik
warnings.warn("Bu API v3'te kaldırılacak", DeprecationWarning)
# FutureWarning — Gelecekte davranış değişecek
warnings.warn("Bu parametre v2'de zorunlu olacak", FutureWarning)
# UserWarning — Genel uyarı (varsayılan)
warnings.warn("Yapılandırma dosyası bulunamadı, varsayılan kullanılıyor")
# RuntimeWarning — Çalışma zamanı uyarısı
warnings.warn("Büyük dosya, işlem uzun sürebilir", RuntimeWarning)Warning Filtreleme
import warnings
# Tüm uyarıları göster
warnings.filterwarnings("always")
# Belirli uyarıları gizle
warnings.filterwarnings("ignore", category=DeprecationWarning)
# Uyarıyı hataya çevir
warnings.filterwarnings("error", category=UserWarning)
try:
warnings.warn("Bu artık hata!", UserWarning)
except UserWarning as e:
print(f"Uyarı hataya dönüştü: {e}")warnings vs Exception
Ne zaman hangisi?
| Durum | Kullan |
|---|---|
| Program devam edemez | raise Exception |
| Girdi geçersiz | raise ValueError |
| API değişecek | warnings.warn(DeprecationWarning) |
| Potansiyel sorun ama program çalışabilir | warnings.warn(UserWarning) |
| Performans uyarısı | warnings.warn(RuntimeWarning) |
⚠️ Dikkat:
DeprecationWarningPython 3.2'den itibaren varsayılan olarak gizlenir (sadece test framework'leri gösterir).FutureWarningise her zaman gösterilir. Kütüphane geliştirirken hangi warning türünü kullandığına dikkat et.
Gerçek Dünya Örneği: Yapılandırma Yöneticisi
Öğrendiklerimizi birleştiren kapsamlı bir örnek yapalım:
import json
import logging
import warnings
from pathlib import Path
# Logger
logger = logging.getLogger(__name__)
# Custom Exception'lar
class YapilandirmaHatasi(Exception):
"""Yapılandırma ile ilgili temel hata."""
pass
class DosyaOkumaHatasi(YapilandirmaHatasi):
"""Yapılandırma dosyası okunamadığında."""
pass
class GecersizYapilandirma(YapilandirmaHatasi):
"""Yapılandırma formatı geçersiz olduğunda."""
def __init__(self, dosya, alan, mesaj):
self.dosya = dosya
self.alan = alan
super().__init__(f"{dosya}: '{alan}' — {mesaj}")
# Yapılandırma Yöneticisi
class YapilandirmaYoneticisi:
ZORUNLU_ALANLAR = ["veritabani", "port", "host"]
def __init__(self, dosya_yolu):
self.dosya_yolu = Path(dosya_yolu)
self.yapilandirma = {}
def yukle(self):
"""Yapılandırma dosyasını yükler."""
try:
with open(self.dosya_yolu) as f:
self.yapilandirma = json.load(f)
except FileNotFoundError:
raise DosyaOkumaHatasi(
f"Dosya bulunamadı: {self.dosya_yolu}"
)
except json.JSONDecodeError as e:
raise GecersizYapilandirma(
str(self.dosya_yolu), "JSON", f"Parse hatası: {e}"
) from e
self._dogrula()
logger.info(f"Yapılandırma yüklendi: {self.dosya_yolu}")
def _dogrula(self):
"""Zorunlu alanları kontrol eder."""
for alan in self.ZORUNLU_ALANLAR:
if alan not in self.yapilandirma:
raise GecersizYapilandirma(
str(self.dosya_yolu), alan, "Zorunlu alan eksik"
)
# Deprecated alan kontrolü
if "eski_parametre" in self.yapilandirma:
warnings.warn(
"'eski_parametre' v3.0'da kaldırılacak, "
"'yeni_parametre' kullanın.",
DeprecationWarning,
stacklevel=3
)
def al(self, anahtar, varsayilan=None):
"""Yapılandırma değerini döndürür."""
return self.yapilandirma.get(anahtar, varsayilan)Kullanımı:
# Ana uygulama
def uygulamayi_baslat():
yapilandirma = YapilandirmaYoneticisi("config.json")
try:
yapilandirma.yukle()
except DosyaOkumaHatasi:
logger.error("Yapılandırma dosyası bulunamadı!")
print("Hata: config.json dosyasını oluşturun.")
return
except GecersizYapilandirma as e:
logger.error(f"Geçersiz yapılandırma: {e}")
print(f"Yapılandırma hatası: {e}")
return
host = yapilandirma.al("host")
port = yapilandirma.al("port")
print(f"Sunucu başlatılıyor: {host}:{port}")
uygulamayi_baslat()Bu örnekte:
Custom exception hiyerarşisi (
YapilandirmaHatasi→ alt sınıflar)Exception chaining (
from e)warningskullanımı (deprecated alan)loggingile hata kaydetmeSpesifik exception yakalama
Exception ile Context Manager
__enter__ ve __exit__ metodlarıyla kendi context manager'ını yazabilirsin. __exit__ metodu exception bilgisini alır:
class ZamanOlcer:
"""İşlem süresini ölçen context manager."""
def __enter__(self):
import time
self.baslangic = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
import time
self.sure = time.time() - self.baslangic
if exc_type is not None:
print(f"Hata oluştu ({self.sure:.2f}s): {exc_val}")
# False dönerse exception tekrar fırlatılır
# True dönerse exception yutulur
return False
print(f"İşlem tamamlandı: {self.sure:.2f}s")
return False
# Kullanım
with ZamanOlcer():
# Uzun süren işlem
toplam = sum(range(10_000_000))
print(f"Toplam: {toplam}")__exit__ metodunun parametreleri:
exc_type: Exception sınıfı (hata yoksaNone)exc_val: Exception nesnesi (hata yoksaNone)exc_tb: Traceback nesnesi (hata yoksaNone)Dönüş değeri
Trueise exception yutulur,Falseise tekrar fırlatılır
assert ile Savunmacı Programlama
assert ifadesi bir koşulun doğru olduğunu garanti eder. Koşul yanlışsa AssertionError fırlatır:
def ortalama_hesapla(sayilar):
assert len(sayilar) > 0, "Liste boş olamaz!"
assert all(isinstance(s, (int, float)) for s in sayilar), \
"Tüm elemanlar sayısal olmalı!"
return sum(sayilar) / len(sayilar)
print(ortalama_hesapla([10, 20, 30])) # 20.0
print(ortalama_hesapla([])) # AssertionError: Liste boş olamaz!Ama dikkat: assert ifadesi production'da devre dışı bırakılabilir (python -O flag'i ile). Bu yüzden assert'ı input doğrulama için değil, geliştirme sırasında varsayımlarını test etmek için kullan.
# DOĞRU — İç tutarlılık kontrolü
def _dahili_hesaplama(veri):
sonuc = karmasik_islem(veri)
assert sonuc >= 0, f"Sonuç negatif olamaz: {sonuc}" # Bug kontrolü
return sonuc
# YANLIŞ — Kullanıcı girdisi doğrulama
def kayit_ol(email):
assert "@" in email # YAPMA! -O ile devre dışı kalır
# Bunun yerine:
if "@" not in email:
raise ValueError("Geçersiz email adresi")💡 İpucu:
assert'ı "bu hiçbir zaman false olmamalı, eğer oluyorsa kodum buglu" durumları için kullan. Kullanıcı girdisi doğrulama içinraise ValueErrorkullan.
Exception Anti-Pattern'leri Tekrar
Son olarak kaçınman gereken pattern'leri özetleyelim:
# 1. Pokemon Exception Handling — "Hepsini yakala!"
try:
# ... bir sürü kod ...
pass
except Exception:
pass # 🙈
# 2. Exception'ı string ile kontrol etme
try:
islem()
except Exception as e:
if "dosya" in str(e): # KÖTÜ — kırılgan, dile bağımlı
pass
# 3. Çok derin try-except iç içe
try:
try:
try:
islem()
except A:
pass
except B:
pass
except C:
pass # Okunabilirlik sıfır
# 4. return finally'de
def kotu_ornek():
try:
return 1
finally:
return 2 # 1'i ezer! Asla yapma.Bunlar yerine temiz, okunaklı, spesifik exception handling yaz. Kodunun gelecekteki okuyucusu (muhtemelen sen) teşekkür edecek.
Özet
Bu derste custom exception'lar ve ileri hata yönetimi tekniklerini öğrendik:
Custom exception tanımlamak
class MyError(Exception): passkadar basit. Exception hiyerarşisi oluşturarak farklı seviyelerde yakalama yapabilirsin.Exception'a ekstra veri eklemek için
__init__metodunu override et vesuper().__init__(mesaj)çağırmayı unutma.Exception chaining (
raise ... from ...) orijinal hatanın kaybolmasını engeller.from Noneise zinciri bilinçli olarak kırar.EAFP (dene-yakala) Python'ın tercih ettiği stildir — LBYL (önce kontrol et) yerine EAFP kullanmak genellikle daha Pythonic ve güvenlidir.
logging modülü production'da hata kaydetmenin doğru yolu.
print()yerinelogger.error()kullan,exc_info=Trueile traceback'i de logla.warnings modülü programı durdurmadan uyarı verir.
DeprecationWarningile eski API'leri kaldırmadan önce kullanıcıları bilgilendir.
AI Asistan
Sorularını yanıtlamaya hazır