4. Lebih Banyak Alat Pengatur Aliran *Control Flow*
***************************************************

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


4.1. Pernyataan "if"
====================

Mungkin tipe pernyataan yang paling terkenal adalah pernyataan "if".
Sebagai contoh:

   >>> 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

Mungkin ada nol atau lebih bagian "elif", dan bagian "else" adalah
opsional. Kata kunci '"elif"' adalah kependekan dari 'else if', dan
berguna untuk menghindari indentasi yang berlebihan. Sebuah "if" ...
"elif" ... "elif" ... adalah urutan pengganti untuk pernyataan
"switch" atau "case" yang ditemukan dalam bahasa lain.

If you're comparing the same value to several constants, or checking
for specific types or attributes, you may also find the "match"
statement useful. For more details see match Statements.


4.2. Pernyataan "for"
=====================

Pernyataan "for" dalam Python sedikit berbeda dari apa yang mungkin
Anda gunakan di C atau Pascal. Alih-alih selalu mengulangi
perkembangan angka dalam aritmatika (seperti dalam Pascal), atau
memberikan pengguna kemampuan untuk menentukan langkah iterasi dan
kondisi berhenti (seperti C), Python pernyataan "for" diulangi pada
item-item dari urutan apa pun (daftar *list* atau string), dalam
urutan yang muncul dalam urutan. Misalnya (tidak ada permainan kata-
kata):

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

Kode yang memodifikasi koleksi *collection* sambil mengulangi koleksi
yang sama bisa sulit untuk diperbaiki. Sebagai gantinya, biasanya
lebih mudah untuk mengulang salinan koleksi atau membuat koleksi baru:

   # 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. Fungsi "range()"
=====================

Jika Anda perlu mengulangi urutan angka, fungsi bawaan "range()"
berguna. Ini menghasilkan urutan *pregressions* aritmatika:

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

Titik akhir yang diberikan tidak pernah menjadi bagian dari urutan
yang dihasilkan; "range(10)" menghasilkan 10 nilai, indeks sah *legal*
untuk item dengan urutan panjang 10. Dimungkinkan untuk membiarkan
rentang mulai dari nomor lain, atau untuk menentukan kenaikan yang
berbeda (bahkan negatif; kadang-kadang ini disebut 'step'):

   >>> 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]

Untuk beralih pada indeks urutan, Anda dapat menggabungkan "range()"
dan "len()" sebagai berikut:

   >>> 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

Dalam kebanyakan kasus seperti itu, bagaimanapun, lebih mudah untuk
menggunakan fungsi "enumerate()", lihat Teknik Perulangan.

Hal aneh terjadi jika Anda hanya mencetak rentang *range*:

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

Dalam banyak hal objek dikembalikan oleh "range()" berperilaku seolah-
olah itu adalah daftar *list*, tetapi sebenarnya tidak. Ini adalah
objek yang mengembalikan item berurutan dari urutan yang diinginkan
ketika Anda mengulanginya, tetapi itu tidak benar-benar membuat daftar
*list*, sehingga menghemat ruang.

Kami mengatakan bahwa objek seperti itu adalah *iterable*, yaitu,
cocok sebagai target untuk fungsi dan konstruksi yang mengharapkan
sesuatu dari mana mereka dapat memperoleh item berturut-turut sampai
pasokan habis. Kita telah melihat bahwa pernyataan "for" adalah
konstruksi seperti itu, sedangkan contoh fungsi yang membutuhkan
sebuah iterable adalah "sum()":

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

Later we will see more functions that return iterables and take
iterables as arguments.  In chapter Struktur Data, we will discuss in
more detail about "list()".


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 Menangani
Pengecualian.


4.6. Pernyataan "pass"
======================

Pernyataan "pass" tidak melakukan apa-apa. Ini dapat digunakan ketika
pernyataan diperlukan secara sintaksis tetapi program tidak memerlukan
tindakan. Sebagai contoh:

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

Ini biasanya digunakan untuk membuat kelas minimal:

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

Tempat lain "pass" dapat digunakan adalah sebagai tempat-penampung
*place-holder* untuk fungsi atau badan bersyarat *conditional body*
saat Anda bekerja pada kode baru, memungkinkan Anda untuk terus
berpikir pada tingkat yang lebih abstrak. "pass" diabaikan secara
diam-diam:

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


4.7. "match" Statements
=======================

A "match" statement takes an expression and compares its value to
successive patterns given as one or more case blocks.  This is
superficially similar to a switch statement in C, Java or JavaScript
(and many other languages), but it's more similar to pattern matching
in languages like Rust or Haskell. Only the first pattern that matches
gets executed and it can also extract components (sequence elements or
object attributes) from the value into variables.

The simplest form compares a subject value against one or more
literals:

   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"

Note the last block: the "variable name" "_" acts as a *wildcard* and
never fails to match. If no case matches, none of the branches is
executed.

You can combine several literals in a single pattern using "|" ("or"):

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

Patterns can look like unpacking assignments, and can be used to bind
variables:

   # 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")

Study that one carefully!  The first pattern has two literals, and can
be thought of as an extension of the literal pattern shown above.  But
the next two patterns combine a literal and a variable, and the
variable *binds* a value from the subject ("point").  The fourth
pattern captures two values, which makes it conceptually similar to
the unpacking assignment "(x, y) = point".

If you are using classes to structure your data you can use the class
name followed by an argument list resembling a constructor, but with
the ability to capture attributes into variables:

   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")

You can use positional parameters with some builtin classes that
provide an ordering for their attributes (e.g. dataclasses). You can
also define a specific position for attributes in patterns by setting
the "__match_args__" special attribute in your classes. If it's set to
("x", "y"), the following patterns are all equivalent (and all bind
the "y" attribute to the "var" variable):

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

A recommended way to read patterns is to look at them as an extended
form of what you would put on the left of an assignment, to understand
which variables would be set to what. Only the standalone names (like
"var" above) are assigned to by a match statement. Dotted names (like
"foo.bar"), attribute names (the "x=" and "y=" above) or class names
(recognized by the "(...)" next to them like "Point" above) are never
assigned to.

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")

We can add an "if" clause to a pattern, known as a "guard".  If the
guard is false, "match" goes on to try the next case block.  Note that
value capture happens before the guard is evaluated:

   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")

Several other key features of this statement:

* Like unpacking assignments, tuple and list patterns have exactly the
  same meaning and actually match arbitrary sequences.  An important
  exception is that they don't match iterators or strings.

* Sequence patterns support extended unpacking: "[x, y, *rest]" and
  "(x, y, *rest)" work similar to unpacking assignments.  The name
  after "*" may also be "_", so "(x, y, *_)" matches a sequence of at
  least two items without binding the remaining items.

* Mapping patterns: "{"bandwidth": b, "latency": l}" captures the
  ""bandwidth"" and ""latency"" values from a dictionary.  Unlike
  sequence patterns, extra keys are ignored.  An unpacking like
  "**rest" is also supported.  (But "**_" would be redundant, so it is
  not allowed.)

* Subpatterns may be captured using the "as" keyword:

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

  will capture the second element of the input as "p2" (as long as the
  input is a sequence of two points)

* Most literals are compared by equality, however the singletons
  "True", "False" and "None" are compared by identity.

* Patterns may use named constants.  These must be dotted names to
  prevent them from being interpreted as capture variable:

     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 :(")

For a more detailed explanation and additional examples, you can look
into **PEP 636** which is written in a tutorial format.


4.8. Mendefinisikan Fungsi
==========================

Kita dapat membuat fungsi yang menulis seri Fibonacci ke batas acak
*arbitrary*:

   >>> 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

Kata kunci "def" memperkenalkan fungsi *definition*. Itu harus diikuti
oleh nama fungsi dan daftar parameter formal yang di dalam tanda
kurung. Pernyataan yang membentuk tubuh fungsi mulai dari baris
berikutnya, dan harus diberi indentasi.

Pernyataan pertama dari tubuh fungsi secara opsional dapat berupa
string literal; string literal ini adalah string dokumentasi fungsi,
atau *docstring*. (Lebih lanjut tentang *docstring* dapat ditemukan di
bagian String Dokumentasi.) Ada alat yang menggunakan *docstring*
untuk secara otomatis menghasilkan dokumentasi online atau cetak, atau
untuk membiarkan pengguna menelusuri kode secara interaktif; itu
praktik yang baik untuk memasukkan dokumen dalam kode yang Anda tulis,
jadi biasakan seperti itu.

*execution* dari suatu fungsi memperkenalkan tabel simbol baru yang
digunakan untuk variabel lokal dari fungsi tersebut. Lebih tepatnya,
semua tugas variabel dalam suatu fungsi menyimpan nilai dalam tabel
simbol lokal; sedangkan referensi variabel pertama-tama terlihat pada
tabel simbol lokal, kemudian pada tabel simbol lokal lampiran
*enclosing* fungsi, kemudian pada tabel simbol global, dan akhirnya
pada tabel nama bawaan. Dengan demikian, variabel global dan variabel
lampiran *enclosing* fungsi tidak dapat secara langsung menetapkan
nilai dalam suatu fungsi (kecuali, untuk variabel global, disebutkan
dalam pernyataan "global", atau, untuk variabel lampiran *enclosing*
fungsi, dinamai dalam pernyataan "nonlocal"), meskipun mungkin
direferensikan.

The actual parameters (arguments) to a function call are introduced in
the local symbol table of the called function when it is called; thus,
arguments are passed using *call by value* (where the *value* is
always an object *reference*, not the value of the object). [1] When a
function calls another function, or calls itself recursively, a new
local symbol table is created for that call.

Definisi fungsi mengasosiasikan nama fungsi dengan objek fungsi dalam
tabel simbol saat ini. Sebuah interpreter dapat mengenali objek yang
ditunjuk dengan nama itu sebagai fungsi yang ditentukan oleh pengguna.
Nama lain juga dapat menunjuk ke objek fungsi yang sama dan juga dapat
digunakan untuk mengakses fungsi tersebut:

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

Berasal dari bahasa lain, Anda mungkin keberatan bahwa "fib" bukan
fungsi melainkan prosedur karena tidak mengembalikan nilai. Bahkan,
fungsi bahkan tanpa pernyataan "return" mengembalikan nilai, meskipun
yang agak membosankan. Nilai ini disebut "None" (ini adalah nama
bawaan). Menulis nilai "None" biasanya dihilangkan *suppressed* oleh
*interpreter* jika itu akan menjadi satu-satunya nilai yang ditulis.
Anda dapat melihatnya jika Anda benar-benar ingin menggunakan
"print()":

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

Sangat mudah untuk menulis fungsi yang mengembalikan daftar *list*
nomor seri Fibonacci, alih-alih mencetaknya:

   >>> 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]

Contoh ini, seperti biasa, menunjukkan beberapa fitur Python baru:

* Pernyataan "return" kembali dengan nilai dari suatu fungsi. "return"
  tanpa argumen ekspresi mengembalikan "None". Keluar dari akhir suatu
  fungsi juga mengembalikan "None".

* 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 Classes) 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. Lebih lanjut tentang Mendefinisikan Fungsi
===============================================

Dimungkinkan juga untuk mendefinisikan fungsi dengan sejumlah variabel
argumen. Ada tiga bentuk, yang bisa digabungkan.


4.9.1. Nilai Argumen Bawaan
---------------------------

Bentuk yang paling berguna adalah menentukan nilai bawaan untuk satu
atau lebih argumen. Ini menciptakan fungsi yang bisa dipanggil dengan
argumen yang lebih sedikit daripada yang didefinisikan untuk
diizinkan. Sebagai contoh:

   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)

Fungsi ini dapat dipanggil dengan beberapa cara:

* hanya memberikan argumen wajib: "ask_ok('Do you really want to
  quit?')"

* memberikan salah satu argumen opsional: "ask_ok('OK to overwrite the
  file?', 2)"

* atau bahkan memberikan semua argumen: "ask_ok('OK to overwrite the
  file?', 2, 'Come on, only yes or no!')"

Contoh ini juga memperkenalkan kata kunci "in". Ini menguji apakah
suatu urutan berisi nilai tertentu atau tidak.

Nilai bawaan dievaluasi pada titik definisi fungsi dalam lingkup
*defining*, sehingga:

   i = 5

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

   i = 6
   f()

akan mencetak "5".

**Peringatan penting:** Nilai bawaan dievaluasi hanya sekali. Ini
membuat perbedaan ketika bawaan adalah objek yang dapat diubah seperti
daftar *list*, kamus *dictionary*, atau *instances* dari sebagian
besar kelas. Misalnya, fungsi berikut mengakumulasi argumen yang
diteruskan pada panggilan berikutnya:

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

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

Ini akan mencetak:

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

Jika Anda tidak ingin bawaan dibagi dengan panggilan berikutnya, Anda
dapat menulis fungsi seperti ini sebagai gantinya:

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


4.9.2. Argumen Kata Kunci *Keyword Arguments*
---------------------------------------------

Fungsi juga dapat dipanggil menggunakan *keyword argument* dari bentuk
"kwarg=value". Misalnya, fungsi berikut:

   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, "!")

menerima satu argumen yang diperlukan ("voltage") dan tiga argumen
opsional ("state", "action", dan "type"). Fungsi ini dapat dipanggil
dengan salah satu cara berikut:

   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

tetapi semua pemanggilan berikut ini tidak valid:

   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

Dalam pemanggilan fungsi, argumen kata kunci *keyword argument* harus
mengikuti argumen posisi. Semua argumen kata kunci *keyword argument*
yang diteruskan harus cocok dengan salah satu argumen yang diterima
oleh fungsi (mis. "actor" bukan argumen yang valid untuk fungsi
"parrot"), dan urutannya tidak penting. Ini juga termasuk argumen non-
opsional (mis. "parrot(voltage=1000)" juga valid). Tidak ada argumen
yang dapat menerima nilai lebih dari sekali. Berikut ini contoh yang
gagal karena batasan ini:

   >>> 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'

Ketika parameter formal terakhir dari bentuk "**name" ada, ia menerima
kamus *dictionary* (lihat Mapping Types --- dict) yang berisi semua
argumen kata kunci *keyword argument* kecuali yang terkait dengan
parameter formal. Ini dapat digabungkan dengan parameter formal dari
bentuk "*name" (dijelaskan dalam subbagian berikutnya) yang menerima
tuple yang berisi argumen posisi di luar daftar parameter formal.
("*name" harus ada sebelum "**name".) Misalnya, jika kita
mendefinisikan fungsi seperti ini:

   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])

Ini bisa disebut seperti ini:

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

dan tentu saja itu akan mencetak:

   -- 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

Perhatikan bahwa bagaimana urutan argumen kata kunci dicetak telah
dijamin sesuai dengan urutan yang disediakan dalam pemanggilan fungsi.


4.9.3. Parameter spesial
------------------------

Secara bawaan, argumen dapat diteruskan ke fungsi Python baik dengan
posisi atau secara eksplisit oleh kata kunci. Untuk keterbacaan dan
kinerja, masuk akal untuk membatasi cara argumen dapat dilewatkan
sehingga pengembang hanya perlu melihat definisi fungsi untuk
menentukan apakah item dilewatkan secara posisi saja, posisi atau kata
kunci, atau kata kunci saja.

Definisi fungsi mungkin terlihat seperti:

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

di mana "/" dan "*" adalah opsional. Jika digunakan, simbol-simbol ini
menunjukkan jenis parameter dengan cara argumen dilewatkan ke fungsi:
posisi-saja, posisi-atau-kata kunci, dan kata kunci-saja. Parameter
kata kunci juga disebut sebagai parameter bernama.


4.9.3.1. Argumen Posisi-atau-Kata Kunci
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Jika "/" dan "*" tidak ada dalam definisi fungsi, argumen dapat
diteruskan ke fungsi dengan posisi atau kata kunci.


4.9.3.2. Parameter Posisi-saja
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Melihat ini sedikit lebih detail, dimungkinkan untuk menandai
parameter tertentu sebagai *positional-only*. Jika *positional-only*,
urutan parameter penting, dan parameter tidak dapat dilewatkan dengan
kata kunci. Parameter posisi-saja ditempatkan sebelum "/" (garis
miring). "/" Digunakan untuk secara logis memisahkan parameter posisi-
saja dari parameter lainnya. Jika tidak ada "/" dalam definisi fungsi,
tidak ada parameter posisi-saja.

Parameter yang mengikuti "/" dapat berupa *positional-or-keyword* atau
*keyword-only*.


4.9.3.3. Argumen Kata Kunci-saja
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Untuk menandai parameter sebagai *keyword-only*, yang menunjukkan
parameter harus dilewatkan dengan argumen kata kunci, tempatkan "*"
dalam daftar argumen tepat sebelum parameter *keyword-only*.


4.9.3.4. Contoh Fungsi
~~~~~~~~~~~~~~~~~~~~~~

Perhatikan definisi fungsi contoh berikut dengan memperhatikan marker
"/" dan "*":

   >>> 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)

Definisi fungsi pertama, "standard_arg", bentuk yang paling akrab,
tidak menempatkan batasan pada konvensi pemanggilan dan argumen dapat
dilewatkan dengan posisi atau kata kunci:

   >>> standard_arg(2)
   2

   >>> standard_arg(arg=2)
   2

Fungsi kedua "pos_only_arg" dibatasi hanya menggunakan parameter
posisi karena ada "/" dalam definisi fungsi:

   >>> 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

Dan yang terakhir menggunakan ketiga konvensi pemanggilan dalam
definisi fungsi yang sama:

   >>> 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'

Akhirnya, pertimbangkan definisi fungsi ini yang memiliki potensi
tabrakan antara argumen posisi "name" dan "**kwds" yang memiliki
"name" sebagai kunci:

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

Tidak ada kemungkinan panggilan yang memungkinkan untuk mengembalikan
ke dalam >>``<<True'' karena kata kunci `` 'nama' 'akan selalu terikat
ke parameter pertama. Sebagai contoh:

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

Tetapi menggunakan "/" (argumen posisi saja), ini dimungkinkan karena
memungkinkan "name" sebagai argumen posisi dan "'name'" sebagai kunci
dalam argumen kata kunci *keyword argument*:

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

Dengan kata lain, nama-nama parameter posisi-saja dapat digunakan
dalam "**kwds" tanpa ambiguitas.


4.9.3.5. Rekap
~~~~~~~~~~~~~~

Contoh kasus dimana akan menentukan parameter mana yang akan digunakan
dalam definisi fungsi:

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

Sebagai pedoman:

* Gunakan posisi-saja jika Anda ingin nama parameter tidak tersedia
  bagi pengguna. Ini berguna ketika nama parameter tidak memiliki arti
  nyata, jika Anda ingin menegakkan urutan argumen ketika fungsi
  dipanggil atau jika Anda perlu mengambil beberapa parameter posisi
  dan kata kunci bergantian *arbitrary*.

* Gunakan kata kunci-saja ketika nama memiliki makna dan definisi
  fungsi lebih mudah dipahami dengan secara eksplisit menggunakan nama
  atau Anda ingin mencegah pengguna mengandalkan posisi argumen yang
  dikirimkan.

* Untuk API, gunakan posisi-saja untuk mencegah perubahan yang merusak
  dari API jika nama parameter diubah di masa mendatang.


4.9.4. Daftar Argumen Berubah-ubah *Arbitrary*
----------------------------------------------

Akhirnya, opsi yang paling jarang digunakan adalah menentukan bahwa
suatu fungsi dapat dipanggil dengan sejumlah argumen acak *arbitrary*.
Argumen-argumen ini akan dibungkus dalam sebuah tuple (lihat
*tuttuples*). Sebelum jumlah variabel argumen, nol atau lebih argumen
normal dapat muncul.

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

Normally, these *variadic* arguments will be last in the list of
formal parameters, because they scoop up all remaining input arguments
that are passed to the function. Any formal parameters which occur
after the "*args" parameter are 'keyword-only' arguments, meaning that
they can only be used as keywords rather than positional arguments.

   >>> 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. Pembukaan Paket *Unpacking* Daftar Argumen
-------------------------------------------------

Situasi sebaliknya terjadi ketika argumen sudah ada dalam daftar
*list* atau tuple tetapi perlu dibongkar untuk panggilan fungsi yang
membutuhkan argumen posisi terpisah. Sebagai contoh, fungsi bawaan
"range()" mengharapkan argumen terpisah *start* dan *stop*. Jika tidak
tersedia secara terpisah, tulis fungsi panggilan dengan operator-"*"
untuk membongkar argumen dari daftar *list* atau tuple:

   >>> 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]

Dengan cara yang sama, kamus dapat mengirimkan argumen kata kunci
dengan operator-"**":

   >>> 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. Ekspresi Lambda
----------------------

Fungsi kecil anonim dapat dibuat dengan kata kunci "lambda". Fungsi
ini mengembalikan jumlah dari dua argumennya: "lambda a, b: a+b".
Fungsi Lambda dapat digunakan di mana pun objek fungsi diperlukan.
Mereka secara sintaksis terbatas pada satu ekspresi. Secara semantik,
mereka hanya pemanis sintaksis untuk definisi fungsi normal. Seperti
definisi fungsi bersarang, fungsi lambda dapat mereferensikan variabel
dari cakupan yang mengandung

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

Contoh di atas menggunakan ekspresi lambda untuk mengembalikan fungsi.
Penggunaan lain adalah untuk melewatkan fungsi kecil sebagai argumen:

   >>> 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. String Dokumentasi
-------------------------

Berikut adalah beberapa konvensi tentang konten dan format string
dokumentasi.

Baris pertama harus selalu berupa ringkasan singkat dan ringkas dari
tujuan objek. Untuk singkatnya, itu tidak boleh secara eksplisit
menyatakan nama atau jenis objek, karena ini tersedia dengan cara lain
(kecuali jika nama tersebut merupakan kata kerja yang menggambarkan
operasi fungsi). Baris ini harus dimulai dengan huruf kapital dan
diakhiri dengan titik.

Jika ada lebih banyak baris dalam string dokumentasi, baris kedua
harus kosong, memisahkan ringkasan secara visual dari sisa deskripsi.
Baris berikut harus satu atau lebih paragraf yang menggambarkan
konvensi pemanggilan objek, efek sampingnya, dll.

Pengurai Python tidak menghapus lekukan dari string multi-baris
literal di Python, jadi alat yang memproses dokumentasi harus
menghapus indentasi jika diinginkan. Ini dilakukan dengan menggunakan
konvensi berikut. Baris tidak-kosong pertama *setelah* baris pertama
string menentukan jumlah indentasi untuk seluruh string dokumentasi.
(Kami tidak dapat menggunakan baris pertama karena umumnya berbatasan
dengan tanda kutip pembukaan string sehingga indentasinya tidak
terlihat dalam string literal.) Spasi "equivalent" untuk indentasi ini
kemudian dihilangkan dari awal semua baris string. Baris yang
indentasi lebih sedikit seharusnya tidak terjadi, tetapi jika terjadi
semua spasi *whitespace* utama harus dihilangkan. Kesetaraan spasi
harus diuji setelah ekspansi tab (hingga 8 spasi, biasanya).

Berikut adalah contoh dari multi-baris *docstring*:

   >>> 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. Anotasi Fungsi
---------------------

Function annotations informasi metadata yang sepenuhnya opsional
tentang jenis yang digunakan oleh fungsi yang ditentukan pengguna
(lihat **PEP 3107** dan **PEP 484** untuk informasi lebih lanjut).

*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: Gaya *Coding*
===============================

Sekarang Anda akan menulis potongan Python yang lebih panjang dan
lebih kompleks, ini adalah saat yang tepat untuk berbicara tentang
*coding style*. Sebagian besar bahasa dapat ditulis (atau lebih
ringkas, *formatted*) dalam gaya yang berbeda; beberapa lebih mudah
dibaca daripada yang lain. Memudahkan orang lain untuk membaca kode
Anda selalu merupakan ide yang baik, dan mengadopsi gaya pengkodean
yang bagus sangat membantu untuk itu.

Untuk Python, **PEP 8** telah muncul sebagai panduan gaya yang
dipatuhi sebagian besar proyek; itu mempromosikan gaya pengkodean yang
sangat mudah dibaca dan menyenangkan. Setiap pengembang Python harus
membacanya di beberapa bagian; di sini adalah poin paling penting yang
ditunjukkan untuk Anda:

* Gunakan lekukan 4-spasi, dan tanpa tab.

  4 spasi adalah kompromi yang baik antara indentasi kecil
  (memungkinkan kedalaman bersarang lebih besar) dan indentasi besar
  (lebih mudah dibaca). Tab menimbulkan kebingungan, dan sebaiknya
  ditinggalkan.

* Bungkus *wrap* garis agar tidak melebihi 79 karakter.

  Ini membantu pengguna dengan tampilan kecil dan memungkinkan untuk
  memiliki beberapa file kode berdampingan pada tampilan yang lebih
  besar.

* Gunakan baris kosong untuk memisahkan fungsi dan kelas, dan blok
  kode yang lebih besar di dalam fungsi.

* Jika memungkinkan, berikan komentar pada baris terkait.

* Gunakan String Dokumentasi *docstrings*.

* Gunakan spasi di sekitar operator dan setelah koma, tetapi tidak
  secara langsung di dalam konstruksi kurung *bracketing*: "a = f(1,
  2) + g(3, 4)".

* Beri nama kelas dan fungsi Anda secara konsisten; konvensi ini
  menggunakan "UpperCamelCase" untuk kelas dan
  "lowercase_with_underscores" untuk fungsi dan metode. Selalu gunakan
  "self" sebagai nama untuk argumen metode pertama (lihat *tut-
  firstclass* untuk lebih lanjut tentang kelas dan metode).

* Jangan gunakan pengkodean ajaib *fancy encodings* jika kode Anda
  dimaksudkan untuk digunakan di lingkungan internasional. Default
  Python, UTF-8, atau bahkan ASCII biasa berfungsi paling baik dalam
  hal apa pun.

* Demikian juga, jangan gunakan karakter non-ASCII dalam
  pengidentifikasi jika hanya ada sedikit kesempatan orang berbicara
  bahasa yang berbeda akan membaca atau merawat kode.

-[ Catatan kaki ]-

[1] Sebenarnya, *call by object reference* akan menjadi deskripsi yang
    lebih baik, karena jika objek yang bisa ditransmisikan dilewatkan,
    pemanggil akan melihat perubahan yang dibuat oleh yang dipanggil
    *callee* (item dimasukkan ke dalam daftar).
