11.1 Tuplelar ve değişebilirlik
Şimdiye kadar iki bileşik tip gördünüz: karakter dizileri, karakterlerden oluşmaktadır; ve listeler, herhangi bir tipte öğelerden oluşmaktadır. Bu iki bileşik tip arasındaki farklardan biri listenin öğelerinin değiştirilebilir olduğu, karakter dizisindeki karakterlerin değiştirilemez olduğuydu. Başka bir deyişle, karakter dizileri değiştirilemez (immutable) ve listeler değiştirilebilir (mutable) tiplerdir.
Tuple, liste gibi herhangi bir tipteki öğelerin ardışıklığıdır. Ancak listelerden farklı olarak tuplelar değiştirilemez. Sözdizim olarak tuplelar virgül ile ayrılmış değerlerin sıralanmasıdır:
1
>>> tup = 2, 4, 6, 8, 10
Her ne kadar gerekmese de, tupleları parantezlerin arasına yazmak gelenekselleşmiştir:
1
>>> tup = (2, 4, 6, 8, 10)
Tek öğeli bir tuple yaratmak için son virgülü yazmamız gerekmektedir:
1
2
3
4
>>> tup = (5,)
>>> type(tup)
<type 'tuple'>
Virgülsüz Python (5)
ifadesine parantezler içerisinde yer alan bir tamsayı olarak davranır:
1
2
3
>>> tup = (5)
>>> type(tup)
<type 'int'>
Sözdizim farklarını bir kenara bırakırsak, tuplelar karakter dizileri ve listelerdeki aynı dizi işlemlerini desteklerler. İndis işleci tupledan belirli bir öğeyi seçmek için kullanılır.
1
2
3
>>> tup = ('a', 'b', 'c', 'd', 'e')
>>> tup[0]
'a'
Dilim işleci de belirli bir aralıktaki öğeleri seçer.
1
2
>>> tup[1:3]
('b', 'c')
Ancak eğer atamayla bir öğeyi değiştirmek istersek, bir hatayla karşılaşırız:
1
2
>>> tup[0] = 'X'
TypeError: 'tuple' object does not support item assignment
Elbette, tuple öğelerini değiştiremesek te, tupleı başka bir tuple ile değiştirebiliriz:
1
2
3
>>> tup = ('X',) + tup[1:]
>>> tup
('X', 'b', 'c', 'd', 'e')
Alternatif olarak, ilk önce listeye çevirip, değiştirip, tekrar bir tuplea dönüştürebiliriz:
1
2
3
4
5
6
7
8
>>> tup = ('X', 'b', 'c', 'd', 'e')
>>> tup = list(tup)
>>> tup
['X', 'b', 'c', 'd', 'e']
>>> tup[0] = 'a'
>>> tup = tuple(tup)
>>> tup
('a', 'b', 'c', 'd', 'e')
11.2 Tuple ataması
Arada sırada, iki değişkenin değerlerini karşılıklı olarak aktarmak yararlıdır. Geleneksel atama cümleleriyle, bir geçici değişken kullanmamız gerekir. Örneğin, a
ve b
‘yi karşılıklı değiştirmek için:
1
2
3
gecici = a
a = b
b = gecici
Eğer bunu sıkça yapmamız gerekirse, bu işlem yorucu olacaktır. Python tuple atamasının bir biçimini bu sorunu çözmek üzere sağlamaktadır:
1
a, b = b, a
Sol taraf değişkenlerin tupleıdır, sağ taraf ise değerlerin. Her bir değer ilgili değişkene atanmaktadır. Sağ taraftaki tüm deyimler herhangi bir atama olmadan önce işlenmektedir. Bu özellik tuple atamasını çok amaçlı kılmaktadır. Ve bu harika bir özelliktir.
Doğal olarak soldaki değişkenlerin ve sağdaki değerlerin sayısı aynı olmalıdır:
1
2
>>> a, b, c, d = 1, 2, 3
ValueError: need more than 3 values to unpack
11.3 Geri dönüş değerleri olarak Tuplelar
Fonksiyonlar tupleları geri dönüş değeri olarak döndürebilirler. Örneğin, iki parametreyi birbiriyle değiştiren bir fonksiyon yazabiliriz:
1
2
def swap(x, y):
return y, x
Daha sonra geri dönüş değerini iki değişkenli bir tuplea atayabiliriz:
1
a, b = swap(a, b)
Bu durumda, swap
i bir fonksiyon yapmanın çok bir avantajı yoktur. Gerçekte, swap
i sarmanın (encapsulate) bir tehlikesi vardır, aşağıdaki cezbedici bir yanlıştır:
1
2
def swap(x, y): # yanlis surum
x, y = y, x
Eğer fonksiyonu şu şekilde çağırırsak:
1
swap(a, b)
a
ve x
aynı değerin rumuzudur. x
‘i swap
içerisinde değiştirmek x
‘in başka bir değeri göstermesini, ancak a
‘nın __main__
içinde etkilenmemesini sağlar. Benzer olarak y
‘i değiştirmenin de b
üzerinde etkisi olmayacaktır.
Bu fonksiyon hata mesajı üretmeden çalışacaktır, ama yapmak istediğimizi yapmayacaktır. Bu tipik bir anlambilim hatasıdır.
11.4 Saf fonksiyonlar ve değiştiriciler - gözden geçirme
9. bölümde listelerle ilişkili olarak saf fonksiyonları ve değiştiricileri (modifier) tartışmıştık. Tuplelar değiştirilemez olduklarına göre üzerlerine bir değiştirici yazamayız.
Burada değiştirici listenin ortasına yeni bir değer eklemektedir:
1
2
3
4
5
6
7
8
#
# seqtools.py
#
def insert_in_middle(val, lst):
middle = len(lst)/2
lst[middle:middle] = [val]
Nasıl çalıştığını görmek için yürütelim:
1
2
3
4
5
>>> from seqtools import *
>>> my_list = ['a', 'b', 'd', 'e']
>>> insert_in_middle('c', my_list)
>>> my_list
['a', 'b', 'c', 'd', 'e']
Tuple ile kullanmak istersek, bir hatayla karşılaşırız:
1
2
3
4
5
6
7
8
>>> my_tuple = ('a', 'b', 'd', 'e')
>>> insert_in_middle('c', my_tuple)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "seqtools.py", line 7, in insert_in_middle
lst[middle:middle] = [val]
TypeError: 'tuple' object does not support item assignment
>>>
Problem tupleların değiştirilemez olması ve dilim atamasını desteklememesidir. insert_in_middle
fonksiyonunu saf fonksiyon yapmanın basit bir yöntemi şudur:
1
2
3
def insert_in_middle(val, tup):
middle = len(tup)/2
return tup[:middle] + (val,) + tup[middle:]
Bu sürüm tuplelar için şimdi çalışacaktır, ama liste veya karakter dizileri için çalışmayacaktır. Eğer tüm dizi tipleri için çalışan bir sürüm istiyorsak, kendi değerimizi uygun dizi tipine saran bir yol bulmalıyız. Basit bir yardımcı fonksiyon işimizi görecektir:
1
2
3
4
5
6
def encapsulate(val, seq):
if type(seq) == type(""):
return str(val)
if type(seq) == type([]):
return [val]
return (val,)
Şimdi insert_in_middle
fonksiyonunu tüm yerleşik dizi tipleriyle çalışacak şekilde yazabiliriz:
1
2
3
def insert_in_middle(val, seq):
middle = len(seq)/2
return seq[:middle] + encapsulate(val, seq) + seq[middle:]
insert_in_middle
fonksiyonunun son iki sürümü saf fonksiyondur. Herhangi bir yan etkileri yoktur. encapsulate
ve insert_in_middle
‘ı seqtools.py
modülüne ekleyerek, sınayabiliriz:
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> from seqtools import *
>>> my_string = 'abde'
>>> my_list = ['a', 'b', 'd', 'e']
>>> my_tuple = ('a', 'b', 'd', 'e')
>>> insert_in_middle('c', my_string)
'abcde'
>>> insert_in_middle('c', my_list)
['a', 'b', 'c', 'd', 'e']
>>> insert_in_middle('c', my_tuple)
('a', 'b', 'c', 'd', 'e')
>>> my_string
'abde'
my_string
, my_list
, ve my_tuple
değerleri değişmedi. insert_in_middle
fonksiyonunun onları değiştirmesini istiyorsak, ürettiğimiz değeri geri dönüş değeri olarak fonksiyondan döndürmeliyiz:
1
2
3
4
>>> my_string = insert_in_middle('c', my_string)
>>> my_string
'abcde'
11.5 Özyineli (rekürsif) veri yapıları
Şu ana kadar gördüğümüz tüm Python veri tipleri listelerde ve tuplelarda değişik yollarla gruplanabilir. Listeler ve tuplelar da içiçe yerleştirilebilir, verinin düzenlenmesi için sayısız olasılık sunar. Kullanımını kolaylaştırmak için verinin düzenlenmesine veri yapısı adı verilir.
Seçim zamanı ve biz de oylar geldikçe hesaplanmasına yardımcı oluyoruz. Oylar farklı şehir, köy, kasaba, ilçe ve illerden geliyor. Bazen toplu oylar, bazen de alt toplamlardan oluşan listeler şeklinde gönderiliyor. Sayımların en iyi ne şekilde saklanacağına dair kafa patlattıktan sonra içiçe sayı listesi kullanmaya karar veriyoruz. İçiçe sayı listesini şu şekilde tanımlıyoruz:
içiçe sayı listesi aşağıdakilerden herhangi biri öğesi olan bir listedir:
- sayılar
- içiçe sayı listesi
Terimin, içiçe sayı listesi kendi tanımını barındırdığına dikkat edin. Bunun gibi özyineli (rekürsif) tanımlamalar matematik ve bilgisayar bilimlerinde yaygındır. Özyineli veri yapılarını tanımlamak için öz ve güçlü bir yol sağlarlar. Özyineli veri yapıları kendilerinin daha küçük ve basit örneklerinden oluşmaktadır. Tanım döngüsel değildir, eninde sonunda öğesi olarak herhangi bir liste içermeyen bir listeye erişeceğiz.
Şimdi içiçe sayı listesindeki tüm değerleri toplayan bir fonksiyon yazmamız gerektiğini varsayalım. Python bir dizideki sayıların toplamını bulmaya yarayan yerleşik bir fonksiyona sahiptir:
1
2
3
4
5
>>> sum([1, 2, 8])
11
>>> sum((3, 5, 8.5))
16.5
>>>
Ancak bizim içiçe sayı listemiz için sum
çalışmayacaktır:
1
2
3
4
5
>>> sum([1, 2, [11, 13], 8])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'list'
>>>
Problem şu ki, listenin üçüncü öğesi, [11, 13]
, bir listedir ve 1
, 2
ve 8
değerlerine eklenemez.
11.6 Özyineleme (rekürsiyon)
Özyineli içiçe sayı listemizdeki tüm sayıları toplamak için listeyi dolaşmak, ve içiçe geçmiş yapısındaki tüm öğeleri dolaşmak, karşılaştığımız her nümerik sayıyı toplamımıza eklemek ve herhangi bir liste öğesi için bu süreci tekrarlamak gerekmektedir.
Modern programlama dilleri genellikle özyinelemeyi desteklemektedir, özyineleme fonksiyonların kendi tanımlarında kendilerini çağırmasıdır. Özyineleme sayesinde, içiçe sayı listesindeki değerlerin toplamını bulacağımız Python kodu şaşırtıcı derecede kısadır:
1
2
3
4
5
6
7
8
def recursive_sum(nested_num_list):
sum = 0
for element in nested_num_list:
if type(element) == type([]):
sum = sum + recursive_sum(element)
else:
sum = sum + element
return sum
recursive_sum
fonksiyonunun gövdesi ağırlıklı olarak nested_num_list
‘i dolaşan bir for
döngüsünden oluşmaktadır. Eğer element
nümerik bir sayı ise (else
dalı), basitçe değeri sum
‘a eklenmektedir. Eğer element
liste ise, recursive_sum
fonksiyonu element
argümanı ile tekrar çağrılmaktadır. Fonksiyon tanımlaması içerisindeki kendisini çağıran cümle özyineli çağrı olarak bilinir.
Özyineleme gerçekten bilgisayar bilimlerindeki en güzel ve en şık araçlardan biridir.
Biraz daha karmaşık bir problem olan içiçe sayı listesindeki en büyük değeri bulmak:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def recursive_max(nested_num_list):
"""
>>> recursive_max([2, 9, [1, 13], 8, 6])
13
>>> recursive_max([2, [[100, 7], 90], [1, 13], 8, 6])
100
>>> recursive_max([2, [[13, 7], 90], [1, 100], 8, 6])
100
>>> recursive_max([[[13, 7], 90], 2, [1, 100], 8, 6])
100
"""
largest = nested_num_list[0]
while type(largest) == type([]):
largest = largest[0]
for element in nested_num_list:
if type(element) == type([]):
max_of_elem = recursive_max(element)
if largest < max_of_elem:
largest = max_of_elem
else: # element liste değilse
if largest < element:
largest = element
return largest
Doctestler recursive_max
çalışmasının örneklerini sağlamak için verilmiştir.
Bu probleme nümerik bir değeri bulmak için eklenmiş olan kıvırma largest
‘in ilklenmesidir. nested_num_list[0]
‘ı kullanamayız, çünkü bu değer sayı da olabilir, bir liste de. Bu sorunu çözmek için largest
‘a hangi derinliğe inerse insin ilk nümerik değeri atayan bir while döngüsü kullanırız.
Yukarıdaki iki örnekte özyineli çağrıya neden olmayan bir temel duruma sahiptir: öğenin bir sayı olduğu, liste olmadığı durum. Temel durum olmadan, sonsuz özyinelemeye girersiniz ve programınız çalışmaz. Python belirli bir maksimum özyineleme derinliğine ulaştığında çalışmayı durdurup, bir çalışma zamanı hatası döndürür.
Aşağıdaki kodu infinite_recursion.py
isimli bir dosyaya yazın:
1
2
3
4
5
6
7
8
#
# infinite_recursion.py
#
def recursion_depth(number):
print("Recursion depth number %d." % number)
recursion_depth(number + 1)
recursion_depth(0)
Programı kaydettiğiniz dizinde komut satırında aşağıdaki komutu çalıştırın:
1
python infinite_recursion.py
Mesajları geçişini izledikten sonra, uzun bir mesaj sonrasında aşağıdakiyle karşılaşacaksınız:
1
2
3
4
...
File "infinite_recursion.py", line 3, in recursion_depth
recursion_depth(number + 1)
RuntimeError: maximum recursion depth exceeded
Bunun gibi bir durumun programlarımızdan birinde oluşmasını hiç bir zaman istemeyiz. Özyinelemeyi anlatmayı bitirmeden önce, Python’da hataların nasıl kotarıldığını öğrenelim.
11.7 İstisnalar (Exceptions)
Her ne zaman bir çalışma zamanı hatası oluşursa, bir istisna ortaya çıkar. Program bu noktada çalışmasını durdurur ve hatanın dökümünü verir, bu döküm ortaya çıkan istisnayla biter.
Örneğin sıfırla bölme aşağıdaki istisnayı yaratır:
1
2
3
4
5
6
>>> print(55/0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
>>>
Var olmayan bir liste öğesine erişmeye çalışmak:
1
2
3
4
5
6
7
>>> a = []
>>> print(a[5])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
>>>
Veya tupleda öğe ataması yapmaya çalışmak:
1
2
3
4
5
6
7
>>> tup = ('a', 'b', 'd', 'd')
>>> tup[2] = 'c'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>>
Her bir durumda, sondaki hata mesajları iki parçadır: iki nokta üstüsteden önce hatanın tipi, ve iki nokta üstüstüden sonra hatanın ayrıntıları.
Bazı durumlarda istisna yaratabilecek işlemler yapmak isteyebiliriz, ancak programın çalışmasını durdurmasını istemeyiz. Bu gibi durumlarda istisna işleme (kotarma, handling) yapmamız gerekir. Bunu da try
ve except
cümleleriyle yaparız.
Örneğin, kullanıcıdan bir dosya ismi sorarız ve daha sonra bu dosyayı açmaya çalışırız. Eğer dosya yoksa, programın çökmesini değil, istisnayı işlemeyi isteriz:
1
2
3
4
5
filename = input('Enter a file name: ')
try:
f = open (filename, "r")
except:
print 'There is no file named', filename
try
cümlesi ilk bloktaki cümleleri işletir. Eğer istisna oluşmazsa, except
bloğunu yoksayar. Eğer herhangi bir istisna oluşursa, except
dalı içerisindeki cümleleri çalıştırır ve devam eder.
Bu yeteneği bir fonksiyon içerisine sarabiliriz: exists
fonksiyonu bir dosya ismi alır ve eğer dosya varsa doğru, yoksa yanlış döndürür:
1
2
3
4
5
6
7
def exists(filename):
try:
f = open(filename)
f.close()
return True
except:
return False
Farklı istisnaları işlemek için birden fazla except
kullanabilirsiniz (istisnalar hakkında ayrıntılı bilgi için Python’u yaratan Guido Van Rossum’un Python Öğrencesindeki Hatalar ve İstisnalar dersini inceleyebilirsiniz).
Eğer programınız hata durumunu tespit ederse, bir istisna tetikleyebilirsiniz. Aşağıda kullanıcıdan girdi alan ve sayının negatif olmadığını kontrol eden bir örnek görebilirsiniz:
1
2
3
4
5
6
7
8
9
#
# learn_exceptions.py
#
def get_age():
age = int(input('Please enter your age: '))
if age < 0:
raise ValueError, '%s is not a valid age' % age
return age
raise
cümlesi iki argüman alır: istisna tipi, ve hata hakkında ayrıntılı bilgi. ValueError
tetiklemek istediğimiz istisnaya en yakın yerleşik istisnadır. Yerleşik istisnaların bütün listesine Python kütüphane referansının 2.3 bölümünden ulaşabilirsiniz (yine Guido Van Rossum’un yazdığı).
Eğer çağırdığı fonksiyon get_age
hatayı kotarırsa, program çalışmaya devam edecektir, diğer durumda Python hata dökümünü yapıp çıkacaktır:
1
2
3
4
5
6
7
8
9
10
11
>>> get_age()
Please enter your age: 42
42
>>> get_age()
Please enter your age: -2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "learn_exceptions.py", line 4, in get_age
raise ValueError, '%s is not a valid age' % age
ValueError: -2 is not a valid age
>>>
Hata mesajı bizim sağladığımız istisna tipini ve ek bilgiyi içermektedir.
İstisna işlemeyi kullanarak, infinite_recursion.py
‘yi değiştirip, izin verilen maksimum özyineleme derinliğine ulaştığında durmasını sağlayabiliriz:
1
2
3
4
5
6
7
8
9
10
11
#
# infinite_recursion.py
#
def recursion_depth(number):
print("Recursion depth number %d." % number)
try:
recursion_depth(number + 1)
except:
print "Maximum recursion depth exceeded."
recursion_depth(0)
Bu sürümü çalıştırıp, sonuçları gözlemleyin.
11.8 Kuyruk özyineleme
Fonksiyon tanımlamasının sonunda bir özyineli çağrı oluşursa, buna kuyruk özyineleme adı verilir.
Aşağıda 6. bölümde yazdığımız gerisayim
fonksiyonunun kuyruk özyineleme ile tekrar yazılmış sürümünü görebilirsiniz:
1
2
3
4
5
6
def gerisayim(n):
if n == 0:
print("Defol!")
else:
print(n)
gerisayim(n-1)
Yineleme ile yapılan herhangi bir hesaplama, özyineleme ile de yapılabilir.
Bir çok bilinen matematiksel fonksiyon özyineli olarak tanımlanmıştır. Faktöriyel, örneğin, özel bir işleçe, !
, sahiptir ve şu şekilde tanımlanmıştır:
1
2
0! = 1
n! = n*(n-1)!
Bunu Pythonda kolayca kodlayabiliriz:
1
2
3
4
5
def faktoryel(n):
if n == 0:
return 1
else:
return n * faktoryel(n-1)
Matematikte çok bilinen diğer bir özyineli ilişki fibonacci serisidir, şu şekilde tanımlanmıştır:
1
2
3
fibonacci(0) = 1
fibonacci(1) = 1
fibonacci(n) = fibonacci(n-1) + fibonacci(n-2)
Bu da Pythonda kolayca yazılabilir:
1
2
3
4
5
def fibonacci (n):
if n == 0 or n == 1:
return 1
else:
return fibonacci(n-1) + fibonacci(n-2)
Hem factorial
hem de fibonacci
kuyruk özyinelemenin örnekleridir.
Kuyruk özyineleme Python gibi dillerde kötü bir alışkanlık olarak kabul edilir, çünkü eşlenik yinelemeli çözüme göre daha fazla sistem kaynağı kullanmaktadır.
faktoryel(1000)
i çalıştırmak maksimum özyineleme derinliğini aşacaktır. Ve fibonacci(35)
çalıştırmayı deneyin, ve hesaplamanın ne kadar uzun sürdüğünü gözlemleyin (sabırla bekleyin, hesaplama bitecektir).
faktoryel
in yinelemeli bir sürümünü yazmanız alıştırma olarak sorulacak, ve bir sonraki bölümde fibonacci
yi daha iyi bir yolla çözeceğiz.
11.9 Liste kavraması
Liste kavraması kısa, matematiksel bir sözdizimi kullanarak diğer listelerden yeni listeler yaratmaya yarayan sözdizimsel bir yapıdır:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> sayilar = [1, 2, 3, 4]
>>> [x**2 for x in sayilar]
[1, 4, 9, 16]
>>> [x**2 for x in sayilar if x**2 > 8]
[9, 16]
>>> [(x, x**2, x**3) for x in sayilar]
[(1, 1, 1), (2, 4, 8), (3, 9, 27), (4, 16, 64)]
>>> dosyalar = ['bin', 'Data', 'Desktop', '.bashrc', '.ssh', '.vimrc']
>>> [isim for isim in dosyalar if isim[0] != '.']
['bin', 'Data', 'Desktop']
>>> harfler = ['a', 'b', 'c']
>>> [n*harf for n in sayilar for harf in harfler]
['a', 'b', 'c', 'aa', 'bb', 'cc', 'aaa', 'bbb', 'ccc', 'aaaa', 'bbbb', 'cccc']
>>>
Liste kavraması için genel sözdizimi şu şekildedir:
1
[expr for item1 in seq1 for item2 in seq2 ... for itemx in seqx if condition]
Bu liste deyimi aşağıdakiyle aynı etkiye sahiptir:
1
2
3
4
5
6
7
8
output_sequence = []
for item1 in seq1:
for item2 in seq2:
...
for itemx in seqx:
if condition:
output_sequence.append(expr)
Gördüğünüz gibi, liste kavraması çok daha kısa ve özdür.
11.10 Mini örnek çalışma: tree
Aşağıdaki program Unix’teki tree programının davranışının bir alt kümesini gerçekleştirmektedir.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#!/usr/bin/env python
import os
import sys
def getroot():
if len(sys.argv) == 1:
path = ''
else:
path = sys.argv[1]
if os.path.isabs(path):
tree_root = path
else:
tree_root = os.path.join(os.getcwd(), path)
return tree_root
def getdirlist(path):
dirlist = os.listdir(path)
dirlist = [name for name in dirlist if name[0] != '.']
dirlist.sort()
return dirlist
def traverse(path, prefix='|--', s='.\n', f=0, d=0):
dirlist = getdirlist(path)
for num, file in enumerate(dirlist):
lastprefix = prefix[:-3] + '`--'
dirsize = len(dirlist)
if num < dirsize - 1:
s += '%s %s\n' % (prefix, file)
else:
s += '%s %s\n' % (lastprefix, file)
path2file = os.path.join(path, file)
if os.path.isdir(path2file):
d += 1
if getdirlist(path2file):
s, f, d = traverse(path2file, '| ' + prefix, s, f, d)
else:
f += 1
return s, f, d
if __name__ == '__main__':
root = getroot()
tree_str, files, dirs = traverse(root)
if dirs == 1:
dirstring = 'directory'
else:
dirstring = 'directories'
if files == 1:
filestring = 'file'
else:
filestring = 'files'
print(tree_str)
print('%d %s, %d %s' % (dirs, dirstring, files, filestring))
Alıştırmaların bir çoğunda bu programı incelemeniz istenecek.
11.11 Sözlük
- değiştirilemez veri tipi
- Değiştirilemeyen veri tipidir. Değiştirilemez tiplerin öğelerine veya dilimlerine yapılan atamalar çalışma zamanı hatası yaratır.
- değiştirilebilir veri tipi
- Değiştirilebilen veri tipleridir. Tüm değiştirilebilen tipler bileşik tiplerdir. Listeler ve sözlükler değiştirilebilir veri tipidir; karakter dizileri ve tuplelar değildir.
- tuple
- Herhangi bir tipte öğe dizisi içeren veri tipidir. Liste gibidir ama değiştirilemez. Tuplelar değiştirilemeyen tiplerin gerektirdiği durumlarda kullanılabilir, örneğin sözlükte (sonraki bölüme bakınız) bir anahtar gibi.
- tuple ataması
- Tek bir atama ifadesiyle tupledaki tüm öğelere atamadır. Tuple ataması sıralı yerine paralel bir şekilde gerçekleşir, değerlerin birbirleriyle değiştirilmesi için yararlıdır.
- veri yapısı
- Kullanımı kolaylaştırmak amacıyla verinin düzenlenmesidir.
- özyineli tanımlama
- Kendisini kendi terimleriyle tanımlayan ifadedir. Yararlı olabilmesi için özyineli olmayan temel durumları içermelidir. Bu şekilde döngüsel tanımlamadan farklıdır. Özyineli tanımlamalar karmaşık veri yapılarını ifade etmenin şık bir yoludur.
- özyineleme
- Hali hazırda çalışan fonksiyonun kendisini çağırmasıdır.
- özyineli çağrı
- Özyineli bir fonksiyonda kendisini çağıran ifade.
- temel durum
- Özyineli fonksiyondaki koşul cümlesinin özyineli çağrı şeklinde sonuçlanmayan bir dalı.
- sonsuz özyineleme
- Temel duruma ulaşmadan sürekli olarak kendisini çağıran fonksiyon. Sonsuz özyineleme çalışma zamanı hatası üretir.
- istisna
- Çalışma zamanında oluşan hata.
- istisna işleme
- Bir istisnanın programın çalışmasını durdurmasını
try
veexcept
cümleleriyle önlemek. - tetikleme
- Bir istisnayı
raise
cümlesiyle üretmek. - kuyruk özyineleme
- Fonksiyon tanımlamasının son cümlesi olarak (kuyrukta) yer alan özyineli çağrı. Kuyruk özyineleme Python programlarında kötü alışkanlık olarak değerlendirilir. Çünkü mantıksal olarak eş fonksiyonlar yineleme kullanılarak daha etkin bir şekilde yazılabilir (ayrıntılı bilgi için tail recursion).
- liste kavrama
- Başka listelerden liste yaratmaya yarayan, matematiksel küme yapım gösterimine benzer sözdizimsel yapı.
11.12 Alıştırmalar
- Örnek program
1
2
3
4
5
6
7
8
9
10
def swap(x, y): # yanlis surum
print ("before swap statement: id(x):", id(x), "id(y):", id(y))
x, y = y, x
print ("after swap statement: id(x):", id(x), "id(y):", id(y))
a, b = 0, 1
print ("before swap function call: id(a):", id(a), "id(b):", id(b))
swap(a, b)
print ("after swap function call: id(a):", id(a), "id(b):", id(b))
Bu programı çalıştırın ve sonuçları tanımlayın. Sonuçları neden bu swap
sürümünün istendiği gibi çalışmadığını açıklamak için kullanın. swap
çağrımından sonra a
ve b
‘nin değerleri ne olacaktır?
seqtools.py
bir modül yaratın. Bu bölümdenencapsulate
veinsert_in_middle
fonksiyonlarını ekleyin.Her üç dizi tipi için doğru çalışıp çalışmadığını sınayan doctestleri ekleyin.Aşağıdaki fonksiyonların herbirini
seqtools.py
dosyasına ekleyin:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
def make_empty(seq):
"""
>>> make_empty([1, 2, 3, 4])
[]
>>> make_empty(('a', 'b', 'c'))
()
>>> make_empty("No, not me!")
''
"""
def insert_at_end(val, seq):
"""
>>> insert_at_end(5, [1, 3, 4, 6])
[1, 3, 4, 6, 5]
>>> insert_at_end('x', 'abc')
'abcx'
>>> insert_at_end(5, (1, 3, 4, 6))
(1, 3, 4, 6, 5)
"""
def insert_in_front(val, seq):
"""
>>> insert_in_front(5, [1, 3, 4, 6])
[5, 1, 3, 4, 6]
>>> insert_in_front(5, (1, 3, 4, 6))
(5, 1, 3, 4, 6)
>>> insert_in_front('x', 'abc')
'xabc'
"""
def index_of(val, seq, start=0):
"""
>>> index_of(9, [1, 7, 11, 9, 10])
3
>>> index_of(5, (1, 2, 4, 5, 6, 10, 5, 5))
3
>>> index_of(5, (1, 2, 4, 5, 6, 10, 5, 5), 4)
6
>>> index_of('y', 'happy birthday')
4
>>> index_of('banana', ['apple', 'banana', 'cherry', 'date'])
1
>>> index_of(5, [2, 3, 4])
-1
>>> index_of('b', ['apple', 'banana', 'cherry', 'date'])
-1
"""
def remove_at(index, seq):
"""
>>> remove_at(3, [1, 7, 11, 9, 10])
[1, 7, 11, 10]
>>> remove_at(5, (1, 4, 6, 7, 0, 9, 3, 5))
(1, 4, 6, 7, 0, 3, 5)
>>> remove_at(2, "Yomrktown")
'Yorktown'
"""
def remove_val(val, seq):
"""
>>> remove_val(11, [1, 7, 11, 9, 10])
[1, 7, 9, 10]
>>> remove_val(15, (1, 15, 11, 4, 9))
(1, 11, 4, 9)
>>> remove_val('what', ('who', 'what', 'when', 'where', 'why', 'how'))
('who', 'when', 'where', 'why', 'how')
"""
def remove_all(val, seq):
"""
>>> remove_all(11, [1, 7, 11, 9, 11, 10, 2, 11])
[1, 7, 9, 10, 2]
>>> remove_all('i', 'Mississippi')
'Msssspp'
"""
def count(val, seq):
"""
>>> count(5, (1, 5, 3, 7, 5, 8, 5))
3
>>> count('s', 'Mississippi')
4
>>> count((1, 2), [1, 5, (1, 2), 7, (1, 2), 8, 5])
2
"""
def reverse(seq):
"""
>>> reverse([1, 2, 3, 4, 5])
[5, 4, 3, 2, 1]
>>> reverse(('shoe', 'my', 'buckle', 2, 1))
(1, 2, 'buckle', 'my', 'shoe')
>>> reverse('Python')
'nohtyP'
"""
def sort_sequence(seq):
"""
>>> sort_sequence([3, 4, 6, 7, 8, 2])
[2, 3, 4, 6, 7, 8]
>>> sort_sequence((3, 4, 6, 7, 8, 2))
(2, 3, 4, 6, 7, 8)
>>> sort_sequence("nothappy")
'ahnoppty'
"""
if __name__ == "__main__":
import doctest
doctest.testmod()
Her zamanki gibi, bu fonksiyonları doctestlerin hepsini geçecek şekilde düzeltin.
- Bir fonksiyon yazın,
recursive_min
, içiçe sayı listesindeki en küçük değeri döndürsün:
1
2
3
4
5
6
7
8
9
10
11
def recursive_min(nested_num_list):
"""
>>> recursive_min([2, 9, [1, 13], 8, 6])
1
>>> recursive_min([2, [[100, 1], 90], [10, 13], 8, 6])
1
>>> recursive_min([2, [[13, -7], 90], [1, 100], 8, 6])
-7
>>> recursive_min([[[-13, 7], 90], 2, [1, 100], 8, 6])
-13
"""
Fonksiyonunuz doctestleri geçmelidir.
- Bir fonksiyon yazın,
recursive_count
,nested_number_list
içerisindetarget
sayısını döndürsün:
1
2
3
4
5
6
7
8
9
10
11
def recursive_count(target, nested_num_list):
"""
>>> recursive_count(2, [2, 9, [2, 1, 13, 2], 8, [2, 6]])
4
>>> recursive_count(7, [[9, [7, 1, 13, 2], 8], [7, 6]])
2
>>> recursive_count(15, [[9, [7, 1, 13, 2], 8], [2, 6]])
0
>>> recursive_count(5, [[5, [5, [1, 5], 5], 5], [5, 6]])
6
"""
Yine fonksiyonunuz tüm doctestleri geçmelidir.
- Bir fonksiyon yazın,
flatten
,nested_number_list
içerisindeki tüm değerleri barındıran basit bir sayı listesi döndürsün:
1
2
3
4
5
6
7
8
9
10
11
def flatten(nested_num_list):
"""
>>> flatten([2, 9, [2, 1, 13, 2], 8, [2, 6]])
[2, 9, 2, 1, 13, 2, 8, 2, 6]
>>> flatten([[9, [7, 1, 13, 2], 8], [7, 6]])
[9, 7, 1, 13, 2, 8, 7, 6]
>>> flatten([[9, [7, 1, 13, 2], 8], [2, 6]])
[9, 7, 1, 13, 2, 8, 2, 6]
>>> flatten([[5, [5, [1, 5], 5], 5], [5, 6]])
[5, 5, 1, 5, 5, 5, 5, 6]
"""
Doctestleri geçtiğini doğrulamak için fonksiyonunuzu çalıştırın.
readposint
isminde bir fonksiyon yazın. Fonksiyon kullanıcıdan bir pozitif tamsayı alsın ve girdiyi gereksinimleri karşılayıp karşılamadığına göre sınasın. Örnek bir oturum aşağıdaki gibi olacaktır:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> num = readposint()
Please enter a positive integer: yes
yes is not a positive integer. Try again.
Please enter a positive integer: 3.14
3.14 is not a positive integer. Try again.
Please enter a positive integer: -6
-6 is not a positive integer. Try again.
Please enter a positive integer: 42
>>> num
42
>>> num2 = readposint("Now enter another one: ")
Now enter another one: 31
>>> num2
31
>>>
Kullanıcının girdisinin yanlış olduğunu doğrulamak için Python’un istisna işleme düzeneklerini kullanın.
Aşağıdakilerin herbiri için Python yorumlayıcının üreteceği sonuçları yazın:
- Liste doldurma 1
1 2 3
>>> nums = [1, 2, 3, 4] >>> [x**3 for x in nums]
- Liste doldurma 2
1 2 3
>>> nums = [1, 2, 3, 4] >>> [x**2 for x in nums if x**2 != 4]
- Liste doldurma 3
1 2
>>> nums = [1, 2, 3, 4] >>> [(x, y) for x in nums for y in nums]
- Liste doldurma 4
1 2
>>> nums = [1, 2, 3, 4] >>> [(x, y) for x in nums for y in nums if x != y]
Yorumlayıcıda denemeden önce sonuçları kavramanız gerekir.
pydoc
veya http://pydoc.org adresindeki çevrimiçi belgeleri kullanaraksys.getrecursionlimit()
vesys.setrecursionlimit(n)
fonksiyonlarının ne iş yaptığını öğrenin. Bu modül fonksiyonlarının nasıl çalıştığını iyice anlamak içininfinite_recursion.py
de olduğu gibi bir çok deney yaratın.faktoryel
fonksiyonunu özyineleme yerine yineleme (iteration) kullanarak tekrar yazın. Fonksiyonunuzu 1000 argümanıyla çağırın ve ne kadar sürede sonucu ürettiğini not edin.İsmi
litter.py
olan bir program yazın, argüman olarak verilen ağacın kökündeki (veya varsayılan olarak verilen o anki dizin) her bir altdizindetrash.txt
isminde boş bir dosya yaratsın.
Şimdi de cleanup.py
isminde bir program yazın, bütün bu dosyaları (dikkat edin, trash.txt
dosyalarını sadece) silsin.
İpucu: Mini örnek çalışmada verdiğimiz tree
programını bu iki özyineli program için temel olarak kullanın.