Python’da id() Fonksiyonu, is İşleci ve Önbellekleme Mekanizması¶
Python’da her nesnenin bir “kimliği” (identity) vardır. Kabaca söylemek gerekirse, bu “kimlik” denen şey esasında o nesnenin bellekteki adresini temsil eder. Python’daki her nesnenin kimliği eşşiz, tek ve benzersizdir. Peki bir nesnenin kimliğine nasıl ulaşırız? Python’da bu işi yapmamızı sağlayacak basit bir fonksiyon bulunur. Bu fonksiyonun adı “id()”. Yani İngilizce’deki “identity” (kimlik) kelimesinin kısaltması. Şimdi örnek bir nesne üzerinde bu id() fonksiyonunu nasıl kullanacağımıza bakalım:
Bildiğiniz gibi Python’da her şey bir nesnedir. Dolayısıyla örnek nesne bulmakta zorlanmayacağız... Herhangi bir “şey”, oluşturduğumuz anda Python açısından bir nesneye dönüşüverecektir zaten:
>>> a = 100
>>> id(a)
137990748
Çıktıda gördüğümüz “137990748” sayısı a değişkeninin tuttuğu “100” sayısının kimliğini gösteriyor. Şimdi id() fonksiyonunu bir de şu şekilde deneyelim:
>>> id(100)
137990748
Gördüğünüz gibi, Python a değişkenini ve 100 sayısını ayrı ayrı sorgulamamıza rağmen aynı kimlik numaralarını gösterdi. Bu demek oluyor ki, Python iki adet “100” sayısı için bellekte iki farklı nesne yaratmıyor. İlk kullanımda önbelleğine aldığı sayıyı, ikinci kez ihtiyaç olduğunda bellekten alıp kullanıyor. Ama bir de şu örneklere bakalım:
>>> a = 1000
>>> id(a)
138406552
>>> id(1000)
137992088
Bu defa Python a değişkeninin tuttuğu 1000 sayısı ile öteki 1000 sayısı için farklı kimlik numaraları gösterdi. Bu demek oluyor ki, Python a değişkeninin tuttuğu 1000 sayısı için ve doğrudan girdiğimiz 1000 sayısı için bellekte iki farklı nesne oluşturuyor. Yani bu iki 1000 sayısı Python açısından birbirinden farklı... Bu durumu görebileceğimiz başka bir yöntem de Python’daki “is” işlecini kullanmaktır. Deneyelim:
>>> a is 1000
False
Gördüğünüz gibi, Python “False” (Yanlış) çıktısını suratımıza bir tokat gibi çarptı... Peki bu ne anlama geliyor? Şöyle ki: Python’da “is” işlecini kullanarak iki nesne arasında karşılaştırma yapmak güvenli değildir. Yani “is” ve “==” işleçleri birbirleriyle aynı işlevi görmez... Bu iki işleç nesnelerin farklı yönlerini sorgular: “is” işleci nesnelerin kimliklerine bakıp o nesnelerin aynı nesneler olup olmadığını kontrol ederken, “==” işleci nesnelerin içeriğine bakarak o nesnelerin aynı değere sahip olup olmadıklarını sorgular. Yani:
>>> a is 1000
False
Ama...
>>> a == 1000
True
Burada “is” işleci a değişkeninin tuttuğu veri ile 1000 sayısının aynı kimlik numarasına sahip olup olmadığını sorgularken, “==” işleci a değişkeninin tuttuğu verinin 1000 olup olmadığını denetliyor. Yani “is” işlecinin yaptığı şey kabaca şu oluyor:
>>> id(a) == id(1000)
False
Şimdiye kadar denediğimiz örnekler hep sayıydı. Şimdi isterseniz bir de karakter dizilerinin durumuna bakalım:
>>> a = "python"
>>> a is "python"
True
Burada “True” çıktısını aldık. Bir de “==” işleci ile bir karşılaştırma yapalım:
>>> a == "python"
True
Bu da normal olarak “True” çıktısı veriyor. Ama şu örneğe bakarsak:
>>> a = "python güçlü ve kolay bir programlama dilidir"
>>> a is "python güçlü ve kolay bir programlama dilidir"
False
Ama...
>>> a == "python güçlü ve kolay bir programlama dilidir"
True
“is” ve “==” işleçlerinin nasıl da farklı sonuçlar verdiğini görüyorsunuz. Çünkü bunlardan biri nesnelerin kimliğini sorgularken, öbürü nesnelerin içeriğini sorguluyor. Ayrıca burada dikkatimizi çekmesi gereken başka bir nokta da “python” karakter dizisinin önbelleğe alınıp gerektiğinde tekrar tekrar kullanılıyorken, “python güçlü ve kolay bir programlama dilidir” karakter dizisinin ise önbelleğe alınmıyor olmasıdır... Aynı karakter dizisinin tekrar kullanılması gerektiğinde Python bunun için bellekte yeni bir nesne daha oluşturuyor...
Peki neden Python, örneğin, 100 sayısını ve “python” karakter dizisini önbelleklerken 1000 sayısını ve “python güçlü ve kolay bir programlama dilidir” karakter dizisini önbelleğe almıyor. Sebebi şu: Python kendi iç mekanizmasının işleyişi gereğince “ufak” nesneleri önbelleğe alırken “büyük” nesneler için her defasında yeni bir depolama işlemi yapıyor. İsterseniz Python açısından “ufak” kavramının sınırının ne olabileceğini şöyle bir kod yardımıyla sorgulayabiliriz:
>>> for k in range(1000):
... for v in range(1000):
... if k is v:
... print k
Bu kod 1000 aralığındaki iki sayı grubunu karşılaştırıp, kimlikleri aynı olan sayıları ekrana döküyor... Yani bir bakıma Python’un hangi sayıya kadar önbellekleme yaptığını gösteriyor. Burada aldığımız sonuca göre şöyle bir denetleme işlemi yapalım:
>>> a = 256
>>> a is 256
True
>>> a = 257
>>> a is 257
False
Dediğimiz gibi, id() fonksiyonu ve dolayısıyla “is” işleci, nesnelerin kimliklerini denetler. Örneğin bir listenin kimliğini şöyle denetleyebiliriz:
>>> liste = ["elma","armut","kebap"]
>>> id(liste)
3082284940L
Bunu başka bir liste üzerinde daha deneyelim:
>>> liste2 = ["elma","armut","kebap"]
>>> id(liste2)
3082284172L
Gördüğünüz gibi, içerik aynı olduğu halde iki listenin kimliği birbirinden farklı... Python bu iki listeyi bellekte iki farklı adreste depoluyor. Ama bu listelerin ait olduğu veritipi (yani “list”), bellekte tek bir adreste depolanacaktır. Çünkü bir nesnenin veritipinin kendisi de başlıbaşına bir nesnedir. Nasıl “liste2” nesnesinin kimlik numarası, bu nesne ortalıkta olduğu sürece aynı kalacaksa, bütün listelerin ait olduğu büyük “list” veritipi nesnesi de tek bir kimlik numarasına sahip olacaktır...
>>> id(type(liste))
>>> 3085544992L
>>> id(type(liste2))
3085544992L
Bu iki çıktı aynıdır, çünkü Python “list” veritipi nesnesi için bellekte tek bir adres kullanıyor. Ama ayrı listeler için ayrı adres kullanıyor... Aynı durum tabii ki öteki veritipleri için de geçerlidir. Mesela “dict” veritipi (sözlük)
>>> sozluk1 = {}
>>> id(sozluk1)
3082285236L
>>> sozluk2 = {}
>>> id(sozluk2)
3082285916L
Ama tıpkı “list” veritipinde olduğu gibi, “dict” veritipinin nesne kimliği de hep aynı olacaktır:
>>> id(type(sozluk1)
3085549888L
>>> id(type(sozluk2)
3085549888L
Peki biz bu bilgiden nasıl yararlanabiliriz? Şöyle: “is” işlecini doğrudan iki nesnenin kendisini karşılaştırmak için kullanamasak da bu nesnelerin veritipini karşılaştırmak için kullanabiliriz. Mesela şöyle bir fonksiyon yazabiliriz:
# -*- coding: utf-8 -*-
def karsilastir(a,b):
if type(a) is type(b):
print "Bu iki nesne aynı veritipine sahiptir"
print "Nesnelerin tipi: %s"%(type(a))
else:
print "Bu iki nesne aynı veritipine sahip DEĞİLDİR!"
print "ilk argümanın tipi: %s"%(type(a))
print "ikinci argümanın tipi: %s"%(type(b))
Burada “if type(a) is type(b):” satırı yerine, tabii ki “if id(type(a)) == id(type(b)):” da yazılabilir... Çünkü “is” işleci, dediğimiz gibi, iki nesnenin kimlikleri üzerinden bir sorgulama işlemi yapıyor.
“is” işlecini veritipi karşılaştırması yapmak için isterseniz şu şekilde de kullanabilirsiniz:
>>> if type(a) is dict:
... sonuca göre bir işlem...
>>> if type(b) is list:
... sonuca göre başka bir işlem...
>>> if type(c) is file:
... sonuca göre daha başka bir işlem...
“is” işlecini aynı zamanda bir nesnenin “None” değerine eş olup olmadığını kontrol etmek için de kullanabilirsiniz. Çünkü “None” değeri bellekte her zaman tek bir adreste depolanacak, dolayısıyla bu değere gönderme yapan bütün nesneler için aynı bellek adresi kullanılacaktır:
>>> if b is None:
... .......
gibi...
Sözün özü, “is” işleci iki nesne arasında içerik karşılaştırması yapmak için güvenli değildir. Çünkü Python bazı nesneleri (özellikle “ufak” boyutlu nesneleri) önbelleğine alırken, bazı nesneler için her defasında farklı bir depolama işlemi yapmaktadır. İçerik karşılaştırması için “==” veya ”!=” işleçlerini kullanmak daha doğru bir yaklaşım olacaktır.