4. Daha Fazla Kontrol Akışı Aracı

As well as the while statement just introduced, Python uses a few more that we will encounter in this chapter.

4.1. if İfadeleri

Belki de en iyi bilinen deyim türü if deyimidir. Örneğin:

>>> x = int(input("Please enter an integer: "))
Please enter an integer: 42
>>> if x < 0:
...     x = 0
...     print('Negative changed to zero')
... elif x == 0:
...     print('Zero')
... elif x == 1:
...     print('Single')
... else:
...     print('More')
...
More

Sıfır veya daha fazla elif bölümü olabilir ve else bölümü isteğe bağlıdır. ‘elif’ anahtar sözcüğü ‘else if’ ifadesinin kısaltmasıdır ve aşırı girintiden kaçınmak için kullanışlıdır. Bir ifelifelif … dizisi, diğer dillerde bulunan switch veya case deyimlerinin yerine geçer.

Aynı değeri birkaç sabitle karşılaştırıyorsanız veya belirli türleri veya nitelikleri kontrol ediyorsanız, match deyimini de yararlı bulabilirsiniz. Daha fazla ayrıntı için pass İfadeleri bölümüne bakınız.

4.2. for İfadeleri

Python’daki for deyimi, C veya Pascal’da alışkın olduğunuzdan biraz farklıdır. Her zaman sayıların aritmetik ilerlemesi üzerinde yineleme yapmak (Pascal’daki gibi) veya kullanıcıya hem yineleme adımını hem de durma koşulunu tanımlama yeteneği vermek (C gibi) yerine, Python’un for deyimi, herhangi bir dizinin (bir liste veya bir dize) öğeleri üzerinde, dizide göründükleri sırayla yineler. Örneğin (kelime oyunu yapmak istemedim):

>>> # Measure some strings:
>>> words = ['cat', 'window', 'defenestrate']
>>> for w in words:
...     print(w, len(w))
...
cat 3
window 6
defenestrate 12

Aynı koleksiyon üzerinde yineleme yaparken bir koleksiyonu değiştiren kodun doğru yazılması zor olabilir. Bunun yerine, koleksiyonun bir kopyası üzerinde döngü yapmak veya yeni bir koleksiyon oluşturmak genellikle daha kolaydır:

# Create a sample collection
users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}

# Strategy:  Iterate over a copy
for user, status in users.copy().items():
    if status == 'inactive':
        del users[user]

# Strategy:  Create a new collection
active_users = {}
for user, status in users.items():
    if status == 'active':
        active_users[user] = status

4.3. range() Fonksiyonu

Bir sayı dizisi üzerinde yineleme yapmanız gerekiyorsa, yerleşik range() fonksiyonu kullanışlı olur. Aritmetik ilerlemeler üretir:

>>> for i in range(5):
...     print(i)
...
0
1
2
3
4

Verilen bitiş noktası asla oluşturulan dizinin bir parçası değildir; range(10) 10 değer üretir, 10 uzunluğundaki bir dizinin öğeleri için yasal indisler. Aralığın başka bir sayıdan başlamasına izin vermek veya farklı bir artış (negatif bile olsa; bazen buna ‘adım’ denir) belirtmek mümkündür:

>>> list(range(5, 10))
[5, 6, 7, 8, 9]

>>> list(range(0, 10, 3))
[0, 3, 6, 9]

>>> list(range(-10, -100, -30))
[-10, -40, -70]

Bir dizinin indisleri üzerinde yineleme yapmak için range() ve len() öğelerini aşağıdaki gibi birleştirebilirsiniz:

>>> a = ['Mary', 'had', 'a', 'little', 'lamb']
>>> for i in range(len(a)):
...     print(i, a[i])
...
0 Mary
1 had
2 a
3 little
4 lamb

Ancak bu tür durumların çoğunda enumerate() fonksiyonunu kullanmak uygundur, bkz Döngü Teknikleri.

Sadece bir aralık yazdırırsanız garip bir şey olur:

>>> range(10)
range(0, 10)

Birçok yönden range() tarafından döndürülen nesne bir listeymiş gibi davranır, ancak aslında öyle değildir. Üzerinde yineleme yaptığınızda istenen dizinin ardışık öğelerini döndüren bir nesnedir, ancak listeyi gerçekten oluşturmaz, böylece yerden tasarruf sağlar.

Böyle bir nesnenin iterable olduğunu, yani arz tükenene kadar ardışık öğeler elde edebilecekleri bir şey bekleyen fonksiyonlar ve yapılar için bir hedef olarak uygun olduğunu söylüyoruz. Daha önce for deyiminin böyle bir yapı olduğunu görmüştük, bir yinelenebilir alan bir fonksiyon örneği ise sum():

>>> sum(range(4))  # 0 + 1 + 2 + 3
6

Daha sonra yinelenebilirleri döndüren ve argüman olarak yinelenebilirleri alan daha fazla fonksiyon göreceğiz. Veri Yapıları bölümünde, list() hakkında daha ayrıntılı olarak tartışacağız.

4.4. break and continue Statements

The break statement breaks out of the innermost enclosing for or while loop:

>>> for n in range(2, 10):
...     for x in range(2, n):
...         if n % x == 0:
...             print(f"{n} equals {x} * {n//x}")
...             break
...
4 equals 2 * 2
6 equals 2 * 3
8 equals 2 * 4
9 equals 3 * 3

The continue statement continues with the next iteration of the loop:

>>> for num in range(2, 10):
...     if num % 2 == 0:
...         print(f"Found an even number {num}")
...         continue
...     print(f"Found an odd number {num}")
...
Found an even number 2
Found an odd number 3
Found an even number 4
Found an odd number 5
Found an even number 6
Found an odd number 7
Found an even number 8
Found an odd number 9

4.5. else Clauses on Loops

In a for or while loop the break statement may be paired with an else clause. If the loop finishes without executing the break, the else clause executes.

In a for loop, the else clause is executed after the loop finishes its final iteration, that is, if no break occurred.

In a while loop, it’s executed after the loop’s condition becomes false.

In either kind of loop, the else clause is not executed if the loop was terminated by a break. Of course, other ways of ending the loop early, such as a return or a raised exception, will also skip execution of the else clause.

This is exemplified in the following for loop, which searches for prime numbers:

>>> for n in range(2, 10):
...     for x in range(2, n):
...         if n % x == 0:
...             print(n, 'equals', x, '*', n//x)
...             break
...     else:
...         # loop fell through without finding a factor
...         print(n, 'is a prime number')
...
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3

(Yes, this is the correct code. Look closely: the else clause belongs to the for loop, not the if statement.)

One way to think of the else clause is to imagine it paired with the if inside the loop. As the loop executes, it will run a sequence like if/if/if/else. The if is inside the loop, encountered a number of times. If the condition is ever true, a break will happen. If the condition is never true, the else clause outside the loop will execute.

When used with a loop, the else clause has more in common with the else clause of a try statement than it does with that of if statements: a try statement’s else clause runs when no exception occurs, and a loop’s else clause runs when no break occurs. For more on the try statement and exceptions, see Özel Durumları İşleme.

4.6. pass İfadeleri

pass deyimi hiçbir şey yapmaz. Sözdizimsel olarak bir deyim gerektiğinde ancak program hiçbir eylem gerektirmediğinde kullanılabilir. Örneğin:

>>> while True:
...     pass  # Busy-wait for keyboard interrupt (Ctrl+C)
...

Bu genellikle minimal sınıflar oluşturmak için kullanılır:

>>> class MyEmptyClass:
...     pass
...

pass ‘in kullanılabileceği bir başka yer de, yeni kod üzerinde çalışırken bir fonksiyon veya koşul gövdesi için bir yer tutucu olarak daha soyut bir düzeyde düşünmeye devam etmenizi sağlamaktır. pass sessizce göz ardı edilir:

>>> def initlog(*args):
...     pass   # Remember to implement this!
...

4.7. pass İfadeleri

match bir ifadeyi alır ve değerini bir veya daha fazla case bloğu olarak verilen ardışık kalıplarla karşılaştırır. Bu, C, Java veya JavaScript’teki (ve diğer birçok dildeki) bir switch ifadesine yüzeysel olarak benzer, ancak Rust veya Haskell gibi dillerdeki kalıp eşleştirmeye daha çok benzer. Yalnızca eşleşen ilk kalıp yürütülür ve ayrıca bileşenleri (sıra öğeleri veya nesne nitelikleri) değerden değişkenlere çıkarabilir.

En basit form, bir konu değerini bir veya daha fazla sabitle karşılaştırır:

def http_error(status):
    match status:
        case 400:
            return "Bad request"
        case 404:
            return "Not found"
        case 418:
            return "I'm a teapot"
        case _:
            return "Something's wrong with the internet"

Son bloğa dikkat edin: “değişken adı” _ bir wildcard görevi görür ve asla eşleşmez. Hiçbir durum eşleşmezse, dallardan hiçbiri yürütülmez.

| (“or”) kullanarak birkaç sabiti tek bir kalıpta birleştirebilirsiniz:

case 401 | 403 | 404:
    return "Not allowed"

Kalıplar paket açma atamaları gibi görünebilir ve değişkenleri bağlamak için kullanılabilir:

# point is an (x, y) tuple
match point:
    case (0, 0):
        print("Origin")
    case (0, y):
        print(f"Y={y}")
    case (x, 0):
        print(f"X={x}")
    case (x, y):
        print(f"X={x}, Y={y}")
    case _:
        raise ValueError("Not a point")

Bunu dikkatle inceleyin! İlk kalıpta iki sabit vardır ve yukarıda gösterilen sabit kalıbının bir uzantısı olarak düşünülebilir. Ancak sonraki iki kalıp bir sabit ve bir değişkeni birleştirir ve değişken özneden (point) bir değer bağlar. Dördüncü kalıp iki değeri yakalar, bu da onu kavramsal olarak (x, y) = point paket açma atamasına benzer hale getirir.

Verilerinizi yapılandırmak için sınıfları kullanıyorsanız, sınıf adını ve ardından bir yapıcıya benzeyen, ancak nitelikleri değişkenlere yakalama yeteneğine sahip bir argüman listesi kullanabilirsiniz:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

def where_is(point):
    match point:
        case Point(x=0, y=0):
            print("Origin")
        case Point(x=0, y=y):
            print(f"Y={y}")
        case Point(x=x, y=0):
            print(f"X={x}")
        case Point():
            print("Somewhere else")
        case _:
            print("Not a point")

Konumsal parametreleri, nitelikleri için bir sıralama sağlayan bazı yerleşik sınıflarla (örneğin veri sınıfları) kullanabilirsiniz. Ayrıca sınıflarınızda __match_args__ niteliğini ayarlayarak kalıplardaki nitelikler için belirli bir konum tanımlayabilirsiniz. Bu özellik (“x”, “y”) olarak ayarlanırsa, bahsi geçen kalıpların hepsi eş değerdir (ve hepsi y niteliğini var değişkenine bağlar):

Point(1, var)
Point(1, y=var)
Point(x=1, y=var)
Point(y=var, x=1)

Kalıpları okumak için önerilen bir yol, hangi değişkenlerin neye ayarlanacağını anlamak için onlara bir atamanın soluna koyacağınız şeyin genişletilmiş bir biçimi olarak bakmaktır. Yalnızca bağımsız isimler (yukarıdaki var gibi) bir eşleştirme deyimi tarafından atanır. Noktalı isimlere (foo.bar gibi), nitelik isimlerine (yukarıdaki x= ve y= gibi) veya sınıf isimlerine (yukarıdaki Point gibi yanlarındaki “(…)” ile tanınan) asla atama yapılmaz.

Patterns can be arbitrarily nested. For example, if we have a short list of Points, with __match_args__ added, we could match it like this:

class Point:
    __match_args__ = ('x', 'y')
    def __init__(self, x, y):
        self.x = x
        self.y = y

match points:
    case []:
        print("No points")
    case [Point(0, 0)]:
        print("The origin")
    case [Point(x, y)]:
        print(f"Single point {x}, {y}")
    case [Point(0, y1), Point(0, y2)]:
        print(f"Two on the Y axis at {y1}, {y2}")
    case _:
        print("Something else")

Bir kalıba “guard” olarak bilinen bir if cümlesi ekleyebiliriz. Eğer guard yanlış ise, match bir sonraki case bloğunu denemeye devam eder. Değer yakalamanın koruma değerlendirilmeden önce gerçekleştiğine dikkat edin:

match point:
    case Point(x, y) if x == y:
        print(f"Y=X at {x}")
    case Point(x, y):
        print(f"Not on the diagonal")

Bu açıklamanın diğer bazı kilit özellikleri:

  • Paket açma atamaları gibi, tuple ve liste kalıpları da tamamen aynı anlama sahiptir ve aslında rastgele dizilerle eşleşir. Önemli bir istisna, yineleyicilerle veya string’lerle eşleşmezler.

  • Sıra kalıpları genişletilmiş paket açmayı destekler: [x, y, *rest] ve (x, y, *rest) paket açma atamalarına benzer şekilde çalışır. * öğesinden sonraki ad _ de olabilir, bu nedenle (x, y, *_) öğesi, kalan öğeleri bağlamadan en az iki öğeden oluşan bir diziyle eşleşir.

  • Eşleme kalıpları: {"bandwidth": b, "latency": l} bir sözlükten "bandwidth" ve "latency" değerlerini yakalar. Sıra kalıplarının aksine, ekstra anahtarlar göz ardı edilir. **rest gibi bir paket açma da desteklenir. (Ancak **_ gereksiz olacağından buna izin verilmez)

  • Alt kalıplar as anahtar sözcüğü kullanılarak yakalanabilir:

    case (Point(x1, y1), Point(x2, y2) as p2): ...
    

    girdinin ikinci elemanını p2 olarak yakalayacaktır (girdi iki noktadan oluşan bir dizi olduğu sürece)

  • Çoğu sabit eşitlikle karşılaştırılır, ancak True, False ve None tekilleri özdeşlikle karşılaştırılır.

  • Kalıplar adlandırılmış sabitler kullanabilir. Bunlar, yakalama değişkeni olarak yorumlanmalarını önlemek için noktalı isimler olmalıdır:

    from enum import Enum
    class Color(Enum):
        RED = 'red'
        GREEN = 'green'
        BLUE = 'blue'
    
    color = Color(input("Enter your choice of 'red', 'blue' or 'green': "))
    
    match color:
        case Color.RED:
            print("I see red!")
        case Color.GREEN:
            print("Grass is green")
        case Color.BLUE:
            print("I'm feeling the blues :(")
    

Daha ayrıntılı bir açıklama ve ek örnekler için, öğretici bir formatta yazılmış olan PEP 636 sayfasına bakabilirsiniz.

4.8. Fonksiyonların Tanımlanması

Fibonacci serisini rastgele bir sınıra kadar yazan bir fonksiyon oluşturabiliriz:

>>> def fib(n):    # write Fibonacci series less than n
...     """Print a Fibonacci series less than n."""
...     a, b = 0, 1
...     while a < n:
...         print(a, end=' ')
...         a, b = b, a+b
...     print()
...
>>> # Now call the function we just defined:
>>> fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

Anahtar kelime def bir fonksiyon tanımını tanıtır. Bunu fonksiyon adı ve parantez içine alınmış resmi parametreler listesi takip etmelidir. Fonksiyonun gövdesini oluşturan ifadeler bir sonraki satırdan başlar ve girintili olmalıdır.

Fonksiyon gövdesinin ilk ifadesi isteğe bağlı olarak bir string literal olabilir; bu string literal fonksiyonun dokümantasyon stringi veya docstring ‘dir. (Docstringler hakkında daha fazla bilgi Dokümantasyon Stringler’i bölümünde bulunabilir.) Otomatik olarak çevrimiçi veya basılı dokümantasyon üretmek veya kullanıcının etkileşimli olarak kodda gezinmesini sağlamak için docstringleri kullanan araçlar vardır; yazdığınız koda docstringler eklemek iyi bir uygulamadır, bu yüzden bunu alışkanlık haline getirin.

Bir fonksiyonun çalıştırılması, fonksiyonun yerel değişkenleri için kullanılan yeni bir sembol tablosu ortaya çıkarır. Daha açık bir ifadeyle, bir fonksiyon içindeki tüm değişken atamaları değeri yerel sembol tablosunda saklar; oysa değişken referansları önce yerel sembol tablosuna, sonra çevreleyen fonksiyonların yerel sembol tablolarına, daha sonra global sembol tablosuna ve son olarak da yerleşik isimler tablosuna bakar. Bu nedenle, global değişkenlere ve çevreleyen fonksiyonların değişkenlerine bir fonksiyon içinde doğrudan değer atanamaz (global değişkenler için bir global deyiminde veya çevreleyen fonksiyonların değişkenleri için bir nonlocal deyiminde isimlendirilmedikçe), ancak bunlara referans verilebilir.

Bir fonksiyon çağrısının gerçek parametreleri (argümanları), çağrıldığında çağrılan fonksiyonun yerel sembol tablosunda tanıtılır; bu nedenle, argümanlar call by value (burada value her zaman bir nesne referans’dır, nesnenin değeri değildir) kullanılarak aktarılır. [1] Bir fonksiyon başka bir fonksiyonu çağırdığında veya kendini tekrarlı olarak çağırdığında, bu çağrı için yeni bir yerel sembol tablosu oluşturulur.

Bir fonksiyon tanımı, fonksiyon adını geçerli sembol tablosundaki fonksiyon nesnesiyle ilişkilendirir. Yorumlayıcı, bu adın işaret ettiği nesneyi kullanıcı tanımlı bir fonksiyon olarak tanır. Diğer isimler de aynı fonksiyon nesnesine işaret edebilir ve fonksiyona erişmek için kullanılabilir:

>>> fib
<function fib at 10042ed0>
>>> f = fib
>>> f(100)
0 1 1 2 3 5 8 13 21 34 55 89

Diğer dillerden geliyorsanız, fib ‘in bir fonksiyon değil, değer döndürmediği için bir prosedür olduğuna itiraz edebilirsiniz. Aslında, return ifadesi olmayan fonksiyonlar bile, oldukça sıkıcı olsa da, bir değer döndürürler. Bu değer None olarak adlandırılır (yerleşik bir isimdir). Normalde None değerinin yazılması, yazılan tek değer olacaksa yorumlayıcı tarafından bastırılır. Eğer gerçekten istiyorsanız print() kullanarak görebilirsiniz:

>>> fib(0)
>>> print(fib(0))
None

Fibonacci serisindeki sayıların listesini döndürebilecek bir fonksiyon yazmak gayet basittir, onun yerine şunu yazdırarak:

>>> def fib2(n):  # return Fibonacci series up to n
...     """Return a list containing the Fibonacci series up to n."""
...     result = []
...     a, b = 0, 1
...     while a < n:
...         result.append(a)    # see below
...         a, b = b, a+b
...     return result
...
>>> f100 = fib2(100)    # call it
>>> f100                # write the result
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

Bu örnek, her zamanki gibi, bazı yeni Python özelliklerini göstermektedir:

  • Bir return deyimi bir fonksiyondan bir değerle döner. return deyimi bir ifade argümanı olmadan None döndürür. Bir fonksiyonun sonundan düşmek de None değerini döndürür.

  • The statement result.append(a) calls a method of the list object result. A method is a function that ‘belongs’ to an object and is named obj.methodname, where obj is some object (this may be an expression), and methodname is the name of a method that is defined by the object’s type. Different types define different methods. Methods of different types may have the same name without causing ambiguity. (It is possible to define your own object types and methods, using classes, see Sınıflar) The method append() shown in the example is defined for list objects; it adds a new element at the end of the list. In this example it is equivalent to result = result + [a], but more efficient.

4.9. İşlev Tanımlama hakkında daha fazla bilgi

Değişken sayıda argüman içeren fonksiyonlar tanımlamak da mümkündür. Birleştirilebilen üç form vardır.

4.9.1. Varsayılan Değişken Değerleri

En kullanışlı biçim, bir veya daha fazla bağımsız değişken için varsayılan bir değer belirtmektir. Bu, izin vermek üzere tanımlandığından daha az sayıda bağımsız değişkenle çağrılabilen bir fonksiyon oluşturur. Örneğin:

def ask_ok(prompt, retries=4, reminder='Please try again!'):
    while True:
        reply = input(prompt)
        if reply in {'y', 'ye', 'yes'}:
            return True
        if reply in {'n', 'no', 'nop', 'nope'}:
            return False
        retries = retries - 1
        if retries < 0:
            raise ValueError('invalid user response')
        print(reminder)

Bu fonksiyon çeşitli yollarla çağrılabilir:

  • sadece zorunlu argümanı vererek: ask_ok('Gerçekten çıkmak istiyor musun?')

  • isteğe bağlı değişkenlerden birini vermek: ask_ok('OK to overwrite the file?', 2)

  • ya da bütün değişkenleri vermek: ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')

Bu örnek ayrıca in anahtar sözcüğünü de tanıtır. Bu, bir dizinin belirli bir değer içerip içermediğini test eder.

Varsayılan değerler tanımlayan kapsamdaki fonksiyon tanımlama noktasında değerlendirilir, böylece

i = 5

def f(arg=i):
    print(arg)

i = 6
f()

5 çıktısını verecektir.

Önemli uyarı: Varsayılan değer yalnızca bir kez değerlendirilir. Varsayılan değer liste, sözlük veya çoğu sınıfın örnekleri gibi değiştirilebilir bir nesne olduğunda bu durum fark yaratır. Örneğin, aşağıdaki fonksiyon sonraki çağrılarda kendisine aktarılan argümanları biriktirir:

def f(a, L=[]):
    L.append(a)
    return L

print(f(1))
print(f(2))
print(f(3))

Bu şu çıktıyı verecektir

[1]
[1, 2]
[1, 2, 3]

Varsayılan değerin sonraki çağrılar arasında paylaşılmasını istemiyorsanız, bunun yerine fonksiyonu şu şekilde yazabilirsiniz:

def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

4.9.2. Anahtar Kelime Değişkenleri

Fonksiyonlar ayrıca kwarg=value şeklinde anahtar kelime argümanları kullanılarak da çağrılabilir. Örneğin, aşağıdaki fonksiyon:

def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
    print("-- This parrot wouldn't", action, end=' ')
    print("if you put", voltage, "volts through it.")
    print("-- Lovely plumage, the", type)
    print("-- It's", state, "!")

bir gerekli argüman (voltage) ve üç isteğe bağlı argüman (state, action ve type) kabul eder. Bu fonksiyon aşağıdaki yollardan herhangi biriyle çağrılabilir:

parrot(1000)                                          # 1 positional argument
parrot(voltage=1000)                                  # 1 keyword argument
parrot(voltage=1000000, action='VOOOOOM')             # 2 keyword arguments
parrot(action='VOOOOOM', voltage=1000000)             # 2 keyword arguments
parrot('a million', 'bereft of life', 'jump')         # 3 positional arguments
parrot('a thousand', state='pushing up the daisies')  # 1 positional, 1 keyword

ancak aşağıdaki tüm çağrılar geçersiz olacaktır:

parrot()                     # required argument missing
parrot(voltage=5.0, 'dead')  # non-keyword argument after a keyword argument
parrot(110, voltage=220)     # duplicate value for the same argument
parrot(actor='John Cleese')  # unknown keyword argument

Bir fonksiyon çağrısında, anahtar kelime argümanları konumsal argümanları takip etmelidir. Aktarılan tüm anahtar sözcük argümanları fonksiyon tarafından kabul edilen argümanlardan biriyle eşleşmelidir (örneğin actor parrot fonksiyonu için geçerli bir argüman değildir) ve sıraları önemli değildir. Buna isteğe bağlı olmayan argümanlar da dahildir (örneğin parrot(voltage=1000) da geçerlidir). Hiçbir argüman birden fazla değer alamaz. İşte bu kısıtlama nedeniyle başarısız olan bir örnek:

>>> def function(a):
...     pass
...
>>> function(0, a=0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: function() got multiple values for argument 'a'

**name biçiminde bir son biçimsel parametre mevcut olduğunda, biçimsel parametreye karşılık gelenler dışındaki tüm anahtar kelime argümanlarını içeren bir sözlük alır (bkz. Mapping Types — dict). Bu, biçimsel parametre tuple listesinin ötesindeki konumsal argümanları içeren bir *name biçimindeki bir biçimsel parametre ile birleştirilebilir (bir sonraki alt bölümde açıklanmıştır). (*name, **name ‘den önce gelmelidir.) Örneğin, aşağıdaki gibi bir fonksiyon tanımlarsak:

def cheeseshop(kind, *arguments, **keywords):
    print("-- Do you have any", kind, "?")
    print("-- I'm sorry, we're all out of", kind)
    for arg in arguments:
        print(arg)
    print("-" * 40)
    for kw in keywords:
        print(kw, ":", keywords[kw])

Şöyle denebilir:

cheeseshop("Limburger", "It's very runny, sir.",
           "It's really very, VERY runny, sir.",
           shopkeeper="Michael Palin",
           client="John Cleese",
           sketch="Cheese Shop Sketch")

ve tabii ki yazdıracaktır:

-- Do you have any Limburger ?
-- I'm sorry, we're all out of Limburger
It's very runny, sir.
It's really very, VERY runny, sir.
----------------------------------------
shopkeeper : Michael Palin
client : John Cleese
sketch : Cheese Shop Sketch

Anahtar sözcük bağımsız değişkenlerinin yazdırılma sırasının, fonksiyon çağrısında sağlandıkları sırayla eşleşmesinin garanti edildiğini unutmayın.

4.9.3. Özel parametreler

Varsayılan olarak, argümanlar bir Python fonksiyonuna ya pozisyona göre ya da açıkça anahtar kelimeye göre aktarılabilir. Okunabilirlik ve performans için, argümanların geçirilme şeklini kısıtlamak mantıklıdır, böylece bir geliştiricinin öğelerin konumla mı, konumla ya da anahtar sözcükle mi yoksa anahtar sözcükle mi geçirildiğini belirlemek için yalnızca fonksiyon tanımına bakması gerekir.

Bir fonksiyon tanımı aşağıdaki gibi görünebilir:

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
      -----------    ----------     ----------
        |             |                  |
        |        Positional or keyword   |
        |                                - Keyword only
         -- Positional only

burada / ve * isteğe bağlıdır. Kullanılırsa, bu semboller, argümanların fonksiyona nasıl geçirilebileceğine göre parametre türünü gösterir: yalnızca konumsal, konumsal veya anahtar sözcük ve yalnızca anahtar sözcük. Anahtar sözcük parametreleri, adlandırılmış parametreler olarak da adlandırılır.

4.9.3.1. Konumsal veya Anahtar Kelime Argümanları

Eğer / ve * fonksiyon tanımında mevcut değilse, argümanlar bir fonksiyona pozisyon veya anahtar kelime ile aktarılabilir.

4.9.3.2. Yalnızca Konumsal Parametreler

Bu konuya biraz daha detaylı bakacak olursak, belirli parametreleri positional-only olarak işaretlemek mümkündür. Eğer konumsal-sadece ise, parametrelerin sırası önemlidir ve parametreler anahtar kelime ile aktarılamaz. Yalnızca konumsal parametreler bir / (ileri eğik çizgi) önüne yerleştirilir. / sadece konumsal parametreleri diğer parametrelerden mantıksal olarak ayırmak için kullanılır. Fonksiyon tanımında / yoksa, sadece konumsal parametre yoktur.

/ işaretini takip eden parametreler konumsal veya anahtar sözcük veya sadece anahtar sözcük olabilir.

4.9.3.3. Yalnızca Anahtar Sözcük İçeren Değişkenler

Parametrelerin anahtar sözcük argümanıyla geçirilmesi gerektiğini belirterek parametreleri anahtar sözcüğe özel olarak işaretlemek için, argüman listesine ilk anahtar sözcüğe özel parametreden hemen önce bir * yerleştirin.

4.9.3.4. Fonksiyon Örnekleri

/ ve * işaretlerine çok dikkat ederek aşağıdaki örnek fonksiyon tanımlarını göz önünde bulundurun:

>>> def standard_arg(arg):
...     print(arg)
...
>>> def pos_only_arg(arg, /):
...     print(arg)
...
>>> def kwd_only_arg(*, arg):
...     print(arg)
...
>>> def combined_example(pos_only, /, standard, *, kwd_only):
...     print(pos_only, standard, kwd_only)

İlk fonksiyon tanımı, standard_arg, en bilinen biçimdir, çağırma kuralına herhangi bir kısıtlama getirmez ve argümanlar konum veya anahtar kelime ile aktarılabilir:

>>> standard_arg(2)
2

>>> standard_arg(arg=2)
2

İkinci fonksiyon pos_only_arg, fonksiyon tanımında bir / olduğu için sadece konumsal parametreleri kullanacak şekilde sınırlandırılmıştır:

>>> pos_only_arg(1)
1

>>> pos_only_arg(arg=1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: pos_only_arg() got some positional-only arguments passed as keyword arguments: 'arg'

The third function kwd_only_arg only allows keyword arguments as indicated by a * in the function definition:

>>> kwd_only_arg(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given

>>> kwd_only_arg(arg=3)
3

Sonuncusu ise aynı fonksiyon tanımında üç çağrı kuralını da kullanır:

>>> combined_example(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: combined_example() takes 2 positional arguments but 3 were given

>>> combined_example(1, 2, kwd_only=3)
1 2 3

>>> combined_example(1, standard=2, kwd_only=3)
1 2 3

>>> combined_example(pos_only=1, standard=2, kwd_only=3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: combined_example() got some positional-only arguments passed as keyword arguments: 'pos_only'

Son olarak, name konumsal argümanı ile name anahtarına sahip **kwds arasında potansiyel bir çakışma olan bu fonksiyon tanımını düşünün:

def foo(name, **kwds):
    return 'name' in kwds

Anahtar kelime 'name' her zaman ilk parametreye bağlanacağı için True döndürmesini sağlayacak olası bir çağrı yoktur. Örneğin:

>>> foo(1, **{'name': 2})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() got multiple values for argument 'name'
>>>

Ancak / (yalnızca konumsal argümanlar) kullanıldığında, name bir konumsal argüman olarak ve 'name' anahtar kelime argümanlarında bir anahtar olarak izin verdiği için mümkündür:

>>> def foo(name, /, **kwds):
...     return 'name' in kwds
...
>>> foo(1, **{'name': 2})
True

Başka bir deyişle, yalnızca konumsal parametrelerin adları **kwds içinde belirsizlik olmadan kullanılabilir.

4.9.3.5. Özet

Kullanım durumu, fonksiyon tanımında hangi parametrelerin kullanılacağını belirleyecektir:

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):

Rehber olarak:

  • Parametrelerin adının kullanıcı tarafından kullanılamamasını istiyorsanız sadece pozisyonel seçeneğini kullanın. Bu, parametre adlarının gerçek bir anlamı olmadığında, fonksiyon çağrıldığında bağımsız değişkenlerin sırasını zorlamak istediğinizde veya bazı konumsal parametreler ve rastgele anahtar sözcükler almanız gerektiğinde kullanışlıdır.

  • Adların bir anlamı olduğunda ve fonksiyon tanımının adlarla açık olmasıyla daha anlaşılır olduğunda veya kullanıcıların geçirilen argümanın konumuna güvenmesini önlemek istediğinizde yalnızca anahtar sözcük kullanın.

  • Bir API için, parametrenin adı gelecekte değiştirilirse API değişikliklerinin bozulmasını önlemek için yalnızca konumsal kullanın.

4.9.4. Keyfi Argüman Listeleri

Son olarak, en az kullanılan seçenek, bir fonksiyonun rastgele sayıda argümanla çağrılabileceğini belirtmektir. Bu argümanlar bir tuple içinde paketlenecektir (bkz Veri Grupları ve Diziler). Değişken argüman sayısından önce, sıfır veya daha fazla normal argüman olabilir.

def write_multiple_items(file, separator, *args):
    file.write(separator.join(args))

Normalde variadic argümanlar biçimsel parametreler listesinde en sonda yer alır, çünkü fonksiyona aktarılan geri kalan tüm girdi argümanlarını toplarlar. *args parametresinden sonra gelen tüm biçimsel parametreler ‘keyword-only’ (yalnızca-anahtar-kelime) argümanlarıdır, yani konumsal argümanlar yerine sadece anahtar kelimeler olarak kullanılabilirler.

>>> def concat(*args, sep="/"):
...     return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'

4.9.5. Argüman Listelerini Açma

Tersi durum, argümanlar zaten bir liste veya tuple içinde olduğunda, ancak ayrı konumsal argümanlar gerektiren bir fonksiyon çağrısı için paketten çıkarılması gerektiğinde ortaya çıkar. Örneğin, yerleşik range() fonksiyonu ayrı start ve stop argümanları bekler. Eğer bunlar ayrı olarak mevcut değilse, argümanları bir listeden veya tuple’dan çıkarmak için fonksiyon çağrısını *-operatörü ile yazın:

>>> list(range(3, 6))            # normal call with separate arguments
[3, 4, 5]
>>> args = [3, 6]
>>> list(range(*args))            # call with arguments unpacked from a list
[3, 4, 5]

Aynı şekilde, sözlükler **-operatörü ile anahtar sözcük argümanları sunabilir:

>>> def parrot(voltage, state='a stiff', action='voom'):
...     print("-- This parrot wouldn't", action, end=' ')
...     print("if you put", voltage, "volts through it.", end=' ')
...     print("E's", state, "!")
...
>>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
>>> parrot(**d)
-- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !

4.9.6. Lambda İfadeleri

Küçük anonim fonksiyonlar lambda anahtar sözcüğü ile oluşturulabilir. Bu fonksiyon iki argümanının toplamını döndürür: lambda a, b: a+b. Lambda fonksiyonları, fonksiyon nesnelerinin gerekli olduğu her yerde kullanılabilir. Sözdizimsel olarak tek bir ifadeyle sınırlıdırlar. Anlamsal olarak, normal bir fonksiyon tanımı için sadece sözdizimsel şekerdirler. İç içe işlev tanımları gibi, lambda işlevleri de içeren kapsamdaki değişkenlere başvurabilir:

>>> def make_incrementor(n):
...     return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43

Yukarıdaki örnekte bir fonksiyon döndürmek için bir lambda ifadesi kullanılmıştır. Başka bir kullanım da küçük bir fonksiyonu argüman olarak geçirmektir:

>>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
>>> pairs.sort(key=lambda pair: pair[1])
>>> pairs
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]

4.9.7. Dokümantasyon Stringler’i

Belge dizelerinin içeriği ve biçimlendirilmesiyle ilgili bazı kurallar aşağıda verilmiştir.

İlk satır her zaman nesnenin amacının kısa ve öz bir özeti olmalıdır. Öz olması için, nesnenin adı veya türü açıkça belirtilmemelidir, çünkü bunlar başka yollarla elde edilebilir (adın bir fonksiyonun çalışmasını açıklayan bir fiil olması durumu hariç). Bu satır büyük harfle başlamalı ve nokta ile bitmelidir.

Belgeleme string’inde daha fazla satır varsa, ikinci satır boş olmalı ve özeti açıklamanın geri kalanından görsel olarak ayırmalıdır. Sonraki satırlar, nesnenin çağrı kurallarını, yan etkilerini vb. açıklayan bir veya daha fazla paragraftan oluşmalıdır.

Python ayrıştırıcısı, Python’daki çok satırlı dize değişmezlerinden girintiyi çıkarmaz, bu nedenle belgeleri işleyen araçların istenirse girintiyi çıkarması gerekir. Bu, aşağıdaki kural kullanılarak yapılır. Dizenin ilk satırından sonraki boş olmayan ilk satır, tüm dokümantasyon dizesi için girinti miktarını belirler. (İlk satırı kullanamayız, çünkü genellikle dizenin açılış tırnaklarına bitişiktir, bu nedenle girintisi dize değişmezinde belirgin değildir) Bu girintiye “eş değer” boşluk daha sonra dizenin tüm satırlarının başlangıcından çıkarılır. Daha az girintili satırlar oluşmamalıdır, ancak oluşurlarsa başlarındaki tüm boşluklar çıkarılmalıdır. Beyaz boşlukların eş değerliği sekmelerin genişletilmesinden sonra test edilmelidir (normalde 8 boşluğa kadar).

İşte çok satırlı bir docstring örneği:

>>> def my_function():
...     """Do nothing, but document it.
...
...     No, really, it doesn't do anything.
...     """
...     pass
...
>>> print(my_function.__doc__)
Do nothing, but document it.

    No, really, it doesn't do anything.

4.9.8. Fonksiyon Ek Açıklamaları

Fonksiyon ek açıklamaları kullanıcı tanımlı fonksiyonlar tarafından kullanılan tipler hakkında tamamen isteğe bağlı meta veri bilgileridir (daha fazla bilgi için PEP 3107 ve PEP 484 sayfalarına bakınız).

Annotations are stored in the __annotations__ attribute of the function as a dictionary and have no effect on any other part of the function. Parameter annotations are defined by a colon after the parameter name, followed by an expression evaluating to the value of the annotation. Return annotations are defined by a literal ->, followed by an expression, between the parameter list and the colon denoting the end of the def statement. The following example has a required argument, an optional argument, and the return value annotated:

>>> def f(ham: str, eggs: str = 'eggs') -> str:
...     print("Annotations:", f.__annotations__)
...     print("Arguments:", ham, eggs)
...     return ham + ' and ' + eggs
...
>>> f('spam')
Annotations: {'ham': <class 'str'>, 'return': <class 'str'>, 'eggs': <class 'str'>}
Arguments: spam eggs
'spam and eggs'

4.10. Intermezzo: Kodlama Stili

Artık daha uzun, daha karmaşık Python parçaları yazmak üzere olduğunuza göre, kodlama stili hakkında konuşmak için iyi bir zaman. Çoğu dil farklı stillerde yazılabilir (ya da daha özlü bir ifadeyle biçimlendirilebilir); bazıları diğerlerinden daha okunaklıdır. Başkalarının kodunuzu okumasını kolaylaştırmak her zaman iyi bir fikirdir ve güzel bir kodlama stili benimsemek buna çok yardımcı olur.

Python için PEP 8, çoğu projenin bağlı olduğu stil kılavuzu olarak ortaya çıkmıştır; okunabilir ve göze hoş gelen bir kodlama stilini teşvik eder. Her Python geliştiricisi bir noktada onu okumalıdır; işte sizin için çıkarılan en önemli noktalar:

  • 4 aralıklı girinti kullanın ve sekme kullanmayın.

    4 boşluk, küçük girinti (daha fazla iç içe geçme derinliği sağlar) ve büyük girinti (okunması daha kolay) arasında iyi bir uzlaşmadır. Sekmeler karışıklığa neden olur ve en iyisi dışarıda bırakmaktır.

  • Satırları 79 karakteri geçmeyecek şekilde sarın.

    Bu, küçük ekranlı kullanıcılara yardımcı olur ve daha büyük ekranlarda birkaç kod dosyasının yan yana olmasını mümkün kılar.

  • Fonksiyonları ve sınıfları ve fonksiyonların içindeki büyük kod bloklarını ayırmak için boş satırlar kullanın.

  • Mümkün olduğunda, yorumları kendi başlarına bir satıra koyun.

  • Docstrings kullanın.

  • Operatörlerin etrafında ve virgüllerden sonra boşluk kullanın, ancak doğrudan parantez yapılarının içinde kullanmayın: a = f(1, 2) + g(3, 4).

  • Sınıflarınızı ve fonksiyonlarınızı tutarlı bir şekilde adlandırın; buradaki kural, sınıflar için UpperCamelCase, fonksiyonlarını; metotlar için de lowercase_with_underscores kullanmaktır. İlk yöntem argümanının adı olarak her zaman self kullanın (sınıflar ve yöntemler hakkında daha fazla bilgi için Sınıflara İlk Bakış bölümüne bakın).

  • Kodunuz uluslararası ortamlarda kullanılacaksa süslü kodlamalar kullanmayın. Python’un varsayılanı, UTF-8 veya hatta düz ASCII her durumda en iyi sonucu verir.

  • Aynı şekilde, farklı bir dil konuşan kişilerin kodu okuması veya muhafaza etmesi için en ufak bir şans varsa, tanımlayıcılarda ASCII olmayan karakterler kullanmayın.

Dipnotlar