Python Programlamaya Giriş 19 - Liste kurma, sözlük kurma, üreteç ifadeler

Python Programlamaya Giriş
Author

Kaan Öztürk

Published

March 24, 2018

Python’da yeni bir liste oluşturmak için, boş bir listeden başlayabiliriz ve bir döngü içinde append() metoduyla büyütebiliriz. Bu yazıda bu işlemi daha hızlı verimli olarak yapmamızı sağlayan özel liste kurma yazımını göreceğiz. Liste kurma, genel olarak bir üreteç ifadesi örneğidir. Liste kurma gibi sözlük ve kümeleri de hızlıca kurmak için benzer bir yazım kullanırız.

Python Programlamaya Giriş yazı dizimizin bütün yazılarına erişmek için Python Programlamaya Giriş kategorimize bakabilirsiniz. Bu dizideki yazılar ayrıca Jupyter defterleri halinde GitHub depomuzda da mevcut.

Basit liste kurma

Basit bir örnekle başlayalım: Bir sayı listesi alalım, ve liste elemanlarının karelerinden oluşan yeni bir liste oluşturalım.

Şimdiye kadar gördüğümüz yöntemlerle bu işi yapmak için önce boş bir liste yaratırız, sonra bir for döngüsü içinde sayıları tek tek alırız, karelerini teker teker boş listeye ekleriz.

sayılar = [1,2,3,4,5]
kareler = []
for x in sayılar:
    kareler.append(x*x)

kareler
[1, 4, 9, 16, 25]

Aynı işi daha kısa yoldan ve daha hızlı yapmak için Python’da liste kurma (list comprehensions) denen bir yapı vardır.

kareler = [x*x for x in sayılar]
kareler
[1, 4, 9, 16, 25]

En basit halinde, liste kurma yapısının genel hali şöyledir:

[ <ifade> for <değişken> in <sıralı nesne> ]

Bu yapıda, <sıralı nesne>’deki değerler tek tek <değişken>’e atanır, ve <ifade>’nin değeri hesaplanarak listeye eklenir.

Yukarıdaki liste kurma ifadesi aşağıdaki kod parçasına denktir:

L = []
for <değişken> in <sıralı nesne>:
    L.append(<ifade>)

Birkaç örnek vererek açıklayalım. İkinin ilk on kuvvetinin listesi:

[2**i for i in range(1,11)]
[2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]

Bir dizedeki karakterleri tek tek almak:

[c+"*" for c in "merhaba"]
['m*', 'e*', 'r*', 'h*', 'a*', 'b*', 'a*']

Çokuzlardaki elemanların toplamlarının listesi:

[ x[0] + x[1] for x in [(1,2), (2,-1), (4,2), (3,7)] ]
[3, 1, 6, 10]

Yukarıdaki örnek, çokuz ataması özelliği sayesinde şöyle de yazılabilir:

[ x+y for x,y in [(1,2), (2,-1), (4,2), (3,7)] ]
[3, 1, 6, 10]

Aradaki fark şöyle: Birinci durumda x değişkenine sırayla çokuzlar atanıyor, sonra indeksleme ile tek tek elemanlarını alıyoruz. İkinci durumda ise çokuzun birinci ve ikinci elemanı sırasıyla x ve y değişkenlerine atanıyor.

İfade olarak bir fonksiyon da kullanılabilir.

def kare(x): return x*x
[kare(x) for x in range(1,11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
def topla(a,b): return a+b
[topla(x,y) for x,y in [(1,2), (2,-1), (4,2), (3,7)] ]
[3, 1, 6, 10]

Liste kurma ifadelerinin döngülerden bir farkı da, döngü değişkeninin kalıcı olmamasıdır.

[i*i for i in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
i
NameError: name 'i' is not defined

Oysa bir döngü kullandığımızda iç değişkenler döngüden sonra da erişilebilir olurlar.

L = []
for i in range(10):
    L.append(i*i)

i
9

Hız farkı

Liste kurma ile, bir döngü içinde bir listeye append() ile eleman ekleme aynı işi yapıyorsa, neden liste kurmayı kullanalım? Birincisi, daha sade bir yapı olduğu için. Daha da önemlisi, liste kurma işlemi daha hızlı çalışır.

Hız farkının iki sebebi vardır: Birincisi Python gibi yorumlanan dillerde döngülerin nispeten yavaş çalışmasıdır (C gibi derlenen dillere göre). Bir liste kurma ifadesi yine de örtük bir döngü içeriyor elbette, ama bu döngü alt seviyededir ve Python yorumlayıcısı tarafından yüksek hızda işletilir. İkinci sebep ise her iterasyonda listenin append() metodunun çağırılması gereğidir - fonksiyon çağrıları uzun zaman alan işlemlerdir ve programı yavaşlatırlar.

Bunu daha somut olarak görmek için 1 ile 1000 arasındaki sayıların karelerini alan bir liste kurma ve bir döngü oluşturalım ve iki yaklaşım arasındaki zaman farkına bakalım.

%%timeit
[x*x for x in range(1000)]
79.8 µs ± 9.52 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%%timeit
L = []
for x in range(1000):
    L.append(x*x)
150 µs ± 13.2 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Şartlı liste kurma


Liste kurma işleminde her elemanı eklemek zorunda değiliz; bir şartı sağlayanları da eklememiz mümkün. Sözgelişi, bir sayı listesi içinde, sadece beşten büyük olanların karesini barındıran bir liste oluşturalım.

[x*x for x in [1,3,5,7,9,11] if x>5 ]
[49, 81, 121]

Bu ifade, aşağıdaki döngüye denktir:

L = []
for x in [1,3,5,7,9,11]:
    if x>5:
        L.append(x*x)

Çeşitli karşılaştırma işlemleri ve mantıksal işlemler de birleştirilebilir. Örnek olarak, 1900 ile 2100 arasındaki artık yılların listesini oluşturalım. Bilindiği gibi bir yıl 4’e bölünebiliyorsa artık yıldır; 100’e bölünen ama 400’e bölünemeyen yıllar hariç. Yani 1600 ve 2000 yılları artık yıl iken, 1700, 1800, 1900, 2100 yılları artık değildir.

[ y for y in range(1900, 2101) if (y%4 == 0 and y%100 != 0) or y%400 == 0 ]
[1904,
 1908,
 1912,
 1916,
 1920,
 1924,
 1928,
 1932,
 1936,
 1940,
 1944,
 1948,
 1952,
 1956,
 1960,
 1964,
 1968,
 1972,
 1976,
 1980,
 1984,
 1988,
 1992,
 1996,
 2000,
 2004,
 2008,
 2012,
 2016,
 2020,
 2024,
 2028,
 2032,
 2036,
 2040,
 2044,
 2048,
 2052,
 2056,
 2060,
 2064,
 2068,
 2072,
 2076,
 2080,
 2084,
 2088,
 2092,
 2096]

Şartlı liste kurmanın genel ifadesi şöyledir:

[<ifade> for <değişken> in <sıralı nesne> if <şart>]

Bu yapıda, sadece şartın doğru olduğu değişken değerleri için listeye eleman eklenir. Şart yanlışsa eklenmez. Eğer şart yanlış olduğunda da listeye belli değerler eklenmesini istiyorsak, daha önce gördüğümüz üçlü if-else ifadesini kullanabiliriz.

Söz gelişi, girdi listesindeki sayı beşten büyükse o sayının karesini, değilse sıfırı barındıran bir liste kuralım.

[x*x if x>5 else 0 for x in [1,3,5,7,9,11] ]
[0, 0, 0, 49, 81, 121]

Buradaki if-else komutunun asıl ifadeye ait olduğuna dikkat edin; liste kurma şartı olarak kullanılmamıştır.

İç içe döngülerle liste kurma

Diyelim iki ayrı listeden elemanları birleştirerek mümkün bütün çiftleri kurmak istiyoruz, (a,1), (a,2),.., (b,1), (b,2),… gibi. Bunun için birinci listedeki her bir eleman için ikinci listedeki elemanlar üzerinde döngü kurmamız gerek. Yani döngü içinde döngü kurmalıyız. Bunu alışıldık yöntemle şöyle yapabiliriz:

liste = []
for c in "abcd":
    for b in [1,2,3]:
        liste.append((c,b))
liste
[('a', 1),
 ('a', 2),
 ('a', 3),
 ('b', 1),
 ('b', 2),
 ('b', 3),
 ('c', 1),
 ('c', 2),
 ('c', 3),
 ('d', 1),
 ('d', 2),
 ('d', 3)]

Aynı işi liste kurma işlemiyle yapmak da mümkündür:

[(c,b) for b in (1,2,3) for c in "abcd"]
[('a', 1),
 ('b', 1),
 ('c', 1),
 ('d', 1),
 ('a', 2),
 ('b', 2),
 ('c', 2),
 ('d', 2),
 ('a', 3),
 ('b', 3),
 ('c', 3),
 ('d', 3)]

Başka bir örnek olarak, iki ayrı listeden alınan sayılar ve onların toplamlarından oluşan çokuzlarla bir liste kurabiliriz.

[(a,b,a+b) for b in (1,2,3) for a in (4,5,6)]
[(4, 1, 5),
 (5, 1, 6),
 (6, 1, 7),
 (4, 2, 6),
 (5, 2, 7),
 (6, 2, 8),
 (4, 3, 7),
 (5, 3, 8),
 (6, 3, 9)]

Kullanabileceğimiz iç içe döngülerin sınırı yok, istediğimiz kadar derine inebiliriz.

[(a,b,c,a+b+c) for c in (1,2) for b in (3,4) for a in (5,6,7)]
[(5, 3, 1, 9),
 (6, 3, 1, 10),
 (7, 3, 1, 11),
 (5, 4, 1, 10),
 (6, 4, 1, 11),
 (7, 4, 1, 12),
 (5, 3, 2, 10),
 (6, 3, 2, 11),
 (7, 3, 2, 12),
 (5, 4, 2, 11),
 (6, 4, 2, 12),
 (7, 4, 2, 13)]

Her for ile beraber bir şart ekleyebiliriz.

[(a,b,a+b) for a in (1,2,3,4) if a>2 for b in (5,6,7)]
[(3, 5, 8), (3, 6, 9), (3, 7, 10), (4, 5, 9), (4, 6, 10), (4, 7, 11)]
[(a,b,a+b) for a in (1,2,3,4) if a>2 for b in (5,6,7) if b<7]
[(3, 5, 8), (3, 6, 9), (4, 5, 9), (4, 6, 10)]

Şartlarımız değişkenlerin hepsini birleştiren bir ifade halinde de olabilir. Örnek olarak, a+b+c==10 şartını sağlayan bütün üçlüleri bulalım. Tekrarlardan kaçınmak için b>=a ve c>=b şartlarını da koyalım.

[ (a,b,c) for a in range(1,10) for b in range(a, 10) for c in range(b,10) if a+b+c==10 ]
[(1, 1, 8),
 (1, 2, 7),
 (1, 3, 6),
 (1, 4, 5),
 (2, 2, 6),
 (2, 3, 5),
 (2, 4, 4),
 (3, 3, 4)]

Liste kurma ifadesinde solda kalan değişken sağ tarafta tanınır, ama tersi doğru değildir. Aşağıdaki kod yanlış olur:

[ (a,b,c) for b in range(a,10) for a in range(1, 10) for c in range(b,10) if a+b+c==10 ]
NameError: name 'a' is not defined

Liste kurmanın yorumlanması sol taraftan başladığı için, yukarıdaki örnekte a henüz tanımlanmamış oluyor ve yorumlayıcı hata mesajı veriyor. Bu davranışı anlamak için yukarıda bir önceki örneği döngülerle yazalım:

L = []
for a in range(1,10):
    for b in range(a,10):
        for c in range(b,10):
            if (a+b+c==10):
                L.append((a,b,c))
          

Buradan da görülebileceği gibi, for b in range(a,10): satırı önce gelirse, a’nın değeri tanımsız oluyor.

Genel liste kurma ifadesi ve dengi olan döngüler

Liste kurma ifadesinin en genel hali şöyle yazılabilir:

[ <ifade> for <değişken_1> in <sıralı_1> if <şart_1>
for <değişken_2> in <sıralı_2> if <şart_2>
...
for <değişken_N> in <sıralı_N> if <şart_N> ]

Burada if kısımları mecburi değil. Bu yapı ilk bakışta yadırgatıcı geliyorsa, aynısının döngü karşılığını göz önünde tutmak faydalı olabilir.

liste = []
for <değişken_1> in <sıralı_1>:
    if <şart_1>:
        for <değişken_2> in <sıralı_2>:
            if <şart_2>:
            ....
                      for <değişken_N> in <sıralı_N>:
                          if <şart_N>:
                              liste.append(<ifade>)

İç içe listeler ve matrisler kurma

Bir önceki bölümde gördüğümüz iç içe for döngülerinin tek seviyeli bir liste kurduğuna dikkat edin. Listeye yeni eleman eklemek ancak en içteki döngüde gerçekleşiyor. Eğer bir listeler listesi oluşturmak istiyorsak başka bir yaklaşım kullanacağız, yani bir liste kurma işlemini başka birinin içine yerleştireceğiz.

[ [a+b for a in "abc"] for b in "xyz"]
[['ax', 'bx', 'cx'], ['ay', 'by', 'cy'], ['az', 'bz', 'cz']]

Bir matris bir listeler listesi olarak yazılabilir. Listenin her elemanı (satır) bir listedir. Sözgelişi, \(\left[\begin{array}{ccc}1&2&3\\ 4&5&6\\7&8&9\end{array}\right]\) matrisini bu şekilde temsil etmek için aşağıdaki yapıyı kullanabiliriz.

[ [3*sütun + satır for satır in [1,2,3] ] for sütun in [0,1,2] ]
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

Daha karmaşık bir örnek olarak, Kronecker delta \(\delta_{i,j}\) matrisi, yani diyagonal elemanları 1, diğer elemanları 0 olan matris bir üçlü if-else ifadesiyle yazılabilir:

[ [ 1 if satır==sütun else 0 for sütun in range(5)] for satır in range(5)]
[[1, 0, 0, 0, 0],
 [0, 1, 0, 0, 0],
 [0, 0, 1, 0, 0],
 [0, 0, 0, 1, 0],
 [0, 0, 0, 0, 1]]

Üreteç ifadeler


Yukarıda gördüğümüz liste kurma yapısı, aslında daha genel ve daha verimli olan üreteç ifadeleri nin (generator expressions) özel bir durumudur. İlk örneğimizi, bir üreteç olarak şöyle yazabiliriz:

(x*x for x in [1,2,3,4,5])
<generator object <genexpr> at 0x7f26157b7a98>

Somut bir listeye çevirmek için bu üreteç ifadesini çıplak halde (çevresinde parantezler olmadan) bir list() fonksiyonuna verebiliriz. Bu, liste kurma ifadesiyle birebir aynı sonucu verir.

list(x*x for x in [1,2,3,4,5])
[1, 4, 9, 16, 25]

Üreteçler, sıralı nesneler gibi iterasyonlarda kullanılabilirler. Önemli bir farkları vardır: Bütün elemanları bir seferde yaratılıp bellekte saklanmaz. Bunun yerine, her eleman sırası geldikçe üretilir. Üreteç en son kaldığı yeri aklında tutar.

g = (x*x for x in [1,2,3,4,5])
next(g), next(g), next(g)
(1, 4, 9)

Üreteci bir döngüde kullanabiliriz:

for i in g:
    print(i)
16
25

Üreteç ifadelerinin listelere göre avantajı daha az yer kaplamalarıdır. Bir listede bütün elemanlar baştan sonra üretilip bellekte saklanırken, bir üreteçte her eleman sadece ihtiyaç duyulduğu anda (söz gelişi, döngüde sırası geldiği zaman) dinamik olarak üretilirler. Özellikle çok elemanlı dizilerde bu önemli miktarda bellek tasarrufu sağlayabilir.

Üreteç ifadeleri, dizili nesne alan fonksiyonlara parametre olarak verilebilir:

sum(1/(x*x) for x in range(1,1001))
1.6439345666815615

Bir de kendi tanımladığımız fonksiyonla kullanalım.

def çarpım(L):
    """Liste elemanlarının çarpımını döndürür."""
    p = 1
    for x in L:
        p *= x
    return p

çarpım(x+2 for x in range(1,6))
2520

Çokuz ve küme kurma

Liste yerine küme kurmak için köşeli parantez [] yerine küme parantezi {} kullanmak yeterlidir. Aynı kurallar geçerlidir.

{i for i in range(10) if i%3 > 0}
{1, 2, 4, 5, 7, 8}

Ancak, yuvarlak parantez () üreteç kurma için kullanıldığından, çokuz üretmek için üreteci tuple() fonksiyonuna vermek gerekir.

tuple( (i,i**2, i**3) for i in range(10) )
((0, 0, 0),
 (1, 1, 1),
 (2, 4, 8),
 (3, 9, 27),
 (4, 16, 64),
 (5, 25, 125),
 (6, 36, 216),
 (7, 49, 343),
 (8, 64, 512),
 (9, 81, 729))

Sözlük kurma

Liste kurma yapısını sözlük kurmaya adapte etmek mümkündür. Sözlük kurma yapısında, ikililerden oluan bir sıralı nesne üzerinden iterasyon yapılır. İkililerin birinci elemanı sözlüğe anahtar, ikinci elemanı ise o anahtara ait değer olarak atanır.

{k:v for k,v in (("a",1),("b",2),("c",6))}
{'a': 1, 'b': 2, 'c': 6}

Anahtarlar ve değerler ayrı nesnelerde sıralanmışlarsa, bunlar zip() fonksiyonuyla istenen biçime sokulabilir.

anahtarlar = ("a","b","c")
değerler = (1,2,3)
{k:v for k,v in zip(anahtarlar,değerler)}
{'a': 1, 'b': 2, 'c': 3}

Bir sözlüğün items() metodu anahtar-değer ikililerinden oluşan bir dizi verir. Bu dizi üzerinden iterasyon yaparsak, mevcut bir sözlükteki verilerle yeni bir sözlük oluşturabiliriz.

Söz gelişi, d sözlüğündeki değerlerin karesini barındıran yeni bir sözlük oluşturalım:

d = {'a': 1, 'b': 2, 'c': 3}
{k*2:v*v for k,v in d.items()}
{'aa': 1, 'bb': 4, 'cc': 9}

Bu yöntemle bir sözlüğün anahtar ve değerlerini ters çevirmek çok kolaydır:

{v:k for k,v in d.items()}
{1: 'a', 2: 'b', 3: 'c'}

Bu son örnekte, iki ayrı anahtarda aynı değer varsa, sonra gelenin öncekini sileceğine dikkat edin.

d = {'a': 1, 'b': 2, 'c': 3, 'd':2}
{v:k for k,v in d.items()}
{1: 'a', 2: 'd', 3: 'c'}