__slots__ mekanizması, dinamik nitelik depolaması için her bir örnek için __dict__ hash tablosu ayıran varsayılan nesne modelinin neden olduğu önemli hafıza yükünü azaltmak amacıyla Python 2.2'de tanıtılmıştır. Problem, milyonlarca nesnenin yalnızca sözlük muhasebesi için yüzlerce megabayt RAM tükettiği yüksek ölçekli uygulamalarda ortaya çıkmaktadır; bu, bellek baskısı ve performansı azaltan önbellek hatalarına neden olur. Çözüm, nitelikler için sabit C dizisi kaydırmaları ayırması için yorumlayıcıya talimat veren bir dizi dize içeren bir sınıf değişkeni olarak __slots__ tanımlamayı içerir. Böylece __dict__ ve __weakref__ slot'larından kaçınılır, yalnızca açıkça talep edilmediği sürece.
Bu optimizasyon, örnek başına hafıza kullanımını yaklaşık %40-50 oranında azaltır ve hashing yükünü ortadan kaldırarak nitelik erişimini hızlandırır. Ayrıca, açıkça eklenmedikçe __weakref__ oluşturulmasını önler, böylece nesne boyutunu daha da küçültür. Ancak, yeni niteliklerin dinamik olarak kazanılmasını engelleyerek katılık getirir ve sınıf hiyerarsileri, sözlük depolamasına sessizce geri dönmemek için slot tutarlılığını korumalıdır.
Gerçek zamanlı bir analitik boru hattı geliştirirken, her bir paketin standart Python nesnesi olarak temsil edildiği saniyede on milyon ağ paketi işleme işlemi sırasında kritik bir hafıza darboğazıyla karşılaştık. Varsayılan __dict__ tabanlı depolama, yalnızca nesne üst yükü için 12GB RAM tüketiyordu. Bu, sıkı bir 10ms gecikme SLA'mızı ihlal eden çöp toplama duraklamalarına neden oldu.
Çözüm 1: Sözlük tabanlı kayıtlar. Paket verilerini sıradan dict örneklerinde depolamayı başlangıçta düşündük. Bu, basitlik ve özel codec'ler olmadan JSON serileştirme sundu, ancak profil oluşturma, sözlük hash tablolarının hala nesne başına 48 byte üst yük gerektirdiğini ve gösterimlerin yalnızca %12 oranında azaldığını ortaya çıkardı. Yöntem kapsüllemenin eksikliği de iş mantığının yardımcı modüller arasında dağılmasına neden oldu.
Çözüm 2: İsimli demetler. collections.namedtuple'a geçiş, demetlerin C yapısı desteğini kullanarak her bir örneğin sözlüklerini ortadan kaldırdı. Bu hafızayı önemli ölçüde azalttı, ancak değişmezlik, analiz sırasında paket zaman damgalarını güncellememizi engelledi ve varsayılan değerler veya doğrulama yöntemleri ekleme eksikliği, garip adaptör desenlerine zorladı.
Çözüm 3: __slots__ sınıfları. Packet sınıfımızı sabit nitelik depolama kullanacak şekilde yeniden yapılandırdık:
class Packet: __slots__ = ('src_ip', 'dst_ip', 'payload', 'timestamp') def __init__(self, src_ip, dst_ip, payload, timestamp): self.src_ip = src_ip self.dst_ip = dst_ip self.payload = payload self.timestamp = timestamp def size(self): return len(self.payload)
Bu, __dict__'yi tamamen ortadan kaldırırken nesne yönelimli tasarımımızı korudu. Bu yaklaşımı seçtik çünkü hafıza verimliliği ile kodun sürdürülebilirliği arasında bir denge sağlıyordu, ancak nesne havuzumuzun zayıf referans önbelleğini desteklemek için '__weakref__''yi açıkça dahil etmemiz gerekti.
Sonuç. Hafıza ayak izi 4.5GB'a düştü ve boru hattının sıradan donanımda çalışmasına olanak tanıdı. Nitelik erişimi, hash tablosu sorguları yerine doğrudan kaydırma hesaplaması sayesinde %35 daha hızlı hale geldi, ancak dinamik nitelik enjeksiyonu için __dict__'ye dayanan hata ayıklama kodunu yeniden yapılandırmamız gerekti.
__slots__, ebeveyn sınıflarının çelişkili slot dizilerini tanımladığı durumlarda çoklu miras ile nasıl etkileşimde bulunur?
Bir çocuk sınıfı __slots__ kullanarak birden fazla ebeveynden miras aldığında, Python, birleşik slot düzeninin çelişen adlar olmadan tutarlı bir doğrusal dizi oluşturmasını gerektirir. Ebeveynler, slotlarında aynı nitelik adlarını paylaşıyorsa veya bir ebeveyn __slots__ kullanırken diğerinin varsayılan __dict__ kullanması durumunda, yorumlayıcı yine de çocuk için bir __dict__ oluşturur, böylece hafıza tasarrufu sağlanmaz. Bu, Python'un ebeveyn slotlarını birleştirerek tek bir slot tablosu oluşturmasından kaynaklanır. Adayların, tüm ebeveynlerin ideal olarak __slots__ kullanması gerektiğini ve çocuğun, sözlük geri dönüşünü önlemek için ek slotları açıkça bildirmesi gerektiğini anlaması önemlidir.
Neden standart pickle modülü, özel durum yöntemleri olmadan slotlu nesneleri yeniden inşa edemez?
Varsayılan olarak, pickle, bir nesnenin durumunu __dict__ niteliği aracılığıyla kaydetmeye ve geri yüklemeye çalışır. Slotlu sınıflar, açıkça eklenmediği sürece bu sözlüğü içermez; bu nedenle, yükleyici, mevcut olmayan slotlara atama yapmaya çalıştığında çıkarım hatası (AttributeError) meydana gelir. Çözüm, slot değerlerinin bir sözlüğünü döndürmek için __getstate__'in uygulanmasını ve bunları geri yüklemek için __setstate__'in kullanılmasını gerektirir ya da __reduce_ex__ protokolünün kullanılmasını gerektirir. Birçok aday, __slots__'ın nesne düzeni sözleşmesini değiştirdiğini gözden kaçırmakta ve pickle'ın otomatik olarak slot tanımlayıcıları üzerinde yansıma kullandığını varsaymaktadır.
__slots__, çalışma zamanında örnek niteliklerinin dinamik olarak eklenmesini engeller mi?
Evet, ancak yalnızca hiçbir ebeveyn sınıfı __dict__ sağlamıyorsa ve '__dict__' slotlar listesine açıkça dahil edilmemişse. Adaylar sıklıkla __slots__'ın yalnızca __dict__ niteliğini kaldırdığını, eğer herhangi bir temel sınıf varsayılan sözlük depolamasını koruyorsa, örneklerin hala o miras alınan sözlük aracılığıyla rastgele nitelikler kabul edebileceğini gözden kaçırmaktadır. Ayrıca, slotlu örnekler mevcut nitelikler açısından değiştirilebilir durumdayken, sınıf seviyesinde hala monkey-patch yapılabilir. Gerçek değişmezlik, __setattr__'ı aşırı yüklemek gibi ek adımlar gerektirir, yalnızca __slots__ kullanmak yeterli değildir.