CPython'da, Python'ın referans uygulamasında, locals() işlevi optimizasyon stratejilerine bağlı olarak yürütme kapsamına göre farklılık gösterir. Modül seviyesinde locals() global isim alanı sözlüğünü geri döndürür, bu da değişkenler için yetkili depolama alanıdır; bu nedenle herhangi bir değişiklik hemen ortamda yansır. Ancak bir işlevin içinde, CPython "hızlı yerel değişkenler" adı verilen bir optimizasyon kullanarak değişkenleri sabit boyutlu bir C dizisinde PyObject* işaretçileri olarak depolar ve bunları bytecode tarafından indeksler, bir hash tablosu yerine. locals() bir işlev içinde çağrıldığında, CPython yeni bir sözlük oluşturur ve bu hızlı yerel diziden değerleri kopyalayarak doldurur, geçici bir anlık görüntü üretir. Sonuç olarak, bu sözlüğe yazmak sadece geçici eşlemeyi günceller; temel hızlı yerel dizi değişmeden kalır, bu nedenle işlev orijinal değişken değerlerini kullanmaya devam eder.
Bir geliştirme ekibi, geliştiricilerin çalışmakta olan bir işlevin kapsamına geçici yardımcı değişkenler eklemelerine olanak tanıyan dinamik bir hata ayıklama aracı geliştirmekteydi. İlk uygulama bir duraklama noktasında locals()'ı yakaladı, döndürülen sözlüğe yardımcı nesneler ekledi ve çalışmakta olan işlevin takip eden satırlarda bu yardımcıları erişmesini bekledi.
İlk yaklaşım, locals() tarafından döndürülen sözlüğü doğrudan değiştirmeye çalıştı, bunun işlevin isim alanı için canlı bir referans olduğu varsayımı altında. Artılar: İşlev imzalarında herhangi bir değişiklik gerektirmedi ve sözdizimsel olarak basit görünüyordu. Eksiler: CPython bu sözlüğü hızlı yerel dizinin salt okunur bir anlık görüntüsü olarak ele aldığı için sessizce başarısız oldu; değişiklikler göz ardı edildi ve gerçek yerel değişkenler değişmeden kaldı.
İkinci strateji, geçici durumu globals() içine eklemeyi içeriyordu; global isim alanını ortak bir duyuru panosu olarak kullanıyordu. Artılar: Bu yöntem verileri uygulama genelinde kalıcı hale getiriyordu ve her yerde erişilebilir hale geliyordu, argüman geçmeye gerek kalmıyordu. Eksiler: Bu ciddi thread güvenliği tehditleri getirdi, global isim alanını geçici hata ayıklama verileriyle kirletti ve dahili durumu tüm işleme açarak kapsülleme ilkelerini ihlal etti.
Son çözüm, enstrümanlı işlevleri açık bir context sözlük argümanı kabul edecek şekilde yeniden yapılandırmak oldu; bu şekilde hata ayıklayıcı değiştirilebilir durumu geçirebiliyordu. Artılar: Bu yaklaşım açıktır, thread güvenlidir ve CPython, PyPy ve Jython arasında aynı şekilde çalışır; Python’ın açık olanın, örtülü olandan daha iyi olduğu ilkesine uyar. Eksiler: Bu, hedef işlevlerin imza ve çağrı yerlerinde değişiklikler gerektirdiği için diğer yaklaşımlardan daha fazla başlangıç yeniden yapılandırması gerektirdi.
Ekip açık context geçiş stratejisini benimsedi. Bu, CPython-özgü uygulama detaylarına bağımlılığı ortadan kaldırdı, isim alanı kirliliğini önledi ve kararlı, çapraz platform uyumlu bir hata ayıklama aracı sağladı.
locals()'ın bir liste kapsamı içinde neden standart bir for-döngüsünden farklı davrandığına dair bir sebep var mı? Python 3'te, liste kapsamları, döngü değişkeninin etrafındaki isim alanına sızmasını önlemek için, bir iç işlev gibi kendi yerel alanlarını tanıtırlar. locals() bir kapsam içinde çağrıldığında, bu geçici kapsam için sözlüğü döndürür, içerme işlevi veya modül için değil. Dahası, normal işlevlerde olduğu gibi, bu sözlük, kapsam kendi kod nesnesi olarak uygulandığında hızlı yerel değişkenlerden bir anlık görüntüdür, bu nedenle ona yazılanlar kalıcı olmaz. Buna karşılık, modül seviyesinde, locals() globals() için bir takma ad, yani canlı modül sözlüğüdür. Bu ayrım kritiktir çünkü geliştiriciler genellikle kapsamların içerdiği blokla aynı yerel isim alanını paylaştığını varsayarlar; bu durum, bu tür durumların hata ayıklama veya içlerine değişken eklemeye çalışırken kafa karışıklığına yol açar.
sys._getframe() kullanarak çerçeve nesnesini manipüle ederek hızlı yerel değişkenlere bir yazma işlemi zorlayabilir misiniz, ve riskler nelerdir? İleri düzey kullanıcılar mevcut yürütme çerçevesine sys._getframe() ile erişebilir ve frame.f_locals'ı değiştirebilir. CPython, bunu yazılabilir bir eşleme olarak açar. Bazı sürümlerde frame.f_locals'a atama yapmak, PyFrame_LocalsToFast gibi iç API'leri kullanarak hızlı yerel diziye bir yazma işlemine neden olabilir, ancak bu davranış uygulama bağımlıdır, sürüm kırılgandır ve dil spesifikasyonunun bir parçası değildir. Riskler arasında eğer referans sayımları doğru yönetilmezse bellek bozulması, optimizatörün güncellenmiş değerleri göz ardı ettiği tutarsız davranışlar ve başka Python uygulamalarında tam bir başarısızlık (örneğin, hızlı yerel dizi mimarisi kullanmayan PyPy) yer alır. Bu tekniğe güvenmek belirsiz davranışlar getirir ve kodun Python sürümleri arasında sürdürülebilir olmasını imkânsız hale getirir.
exec() veya eval() gibi açık yerel değişkenler ile mevcut olduğunda hızlı yerel değişkenlerin optimizasyonunu nasıl etkiler? Bir işlev gövdesinde yerel isim alanını referans alan bir exec() veya eval() çağrısı varsa, CPython, değişkenlerin yalnızca optimize edilmiş hızlı yerel diziden erişileceğini garanti edemez; yürütülen dize dinamik olarak değişkenler ekleyebilir veya silebilir. Bu durumu göz önünde bulundurmak için derleyici, o işlev için hızlı yerel optimizasyonunu devre dışı bırakır, tüm yerel değişkenleri her erişim için danışılan standart bir sözlükte depolamaya geri döner. Bu "optimize edilmemiş" modda, locals() bu gerçek sözlüğü döndürür, bu da değişikliklerin hemen kalıcı olduğu canlı, değiştirilebilir bir görünüm sunar. Bu durumda exec() kullanan kodların daha yavaş çalışmasının ve locals()'ın böyle işlevlerde "doğru" (yazmalara izin veren) çalışıyormuş gibi görünmesinin nedeni budur; optimize edilmiş işlevlerde ise böyle değildir.