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 "if" ... "elif"
... "elif" ... 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 ]-

[1] Aslında, *nesne referansı ile çağırma* daha iyi bir tanımlama
    olacaktır, çünkü değiştirilebilir bir nesne aktarılırsa, çağıran,
    çağırılanın üzerinde yaptığı tüm değişiklikleri (bir listeye
    eklenen öğeler) görecektir.
