Контрольні структури

Окрім твердження while, про яке щойно йшлося, Пітон знає звичайні контрольні структури, що застосовуються і в інших мовах програмування (хоча і з певними "відхиленнями").

Твердження if 

Чи ненайвідомішим твердженням є if. Наприклад:

>>> x = int(raw_input("Прошу ввести ціле число: "))
>>> if x < 0:
...     x = 0
...     print 'Негативне число замінено на нуль'
... elif x == 0:
...     print 'Нуль'
... elif x == 1:
...     print 'Один'
... else:
...     print 'Більше'
...

Це твердження може мати нуль або більше частин elif, частина else - необов'язкова. Ключове слово elif - це скорочення "else if" ("інакше якщо"), його стисла форма запобігає надмірному виділенню пробілами. Послідовність if ... elif ... elif ... є відповідником тверджень switch та case, що зустрічаються в інших мовах програмування.

Твердження for

Твердження for у Пітоні трохи відрізняється від того, до якого ви могли звикнути, використовуючи C або Pascal. Замість постійного перебору чисел арифметичеої прогресії (як у Pascal) чи надання користувачеві можливості визначати як крок перебору (ітерації), так і кінцеву умову (як у C), твердження for у мові Пітон перебирає члени будь-якої послідовності (списку чи ланцюжка) у порядку їхнього в ній розташування. Наприклад:

>>> # Довжини ланцюжків:
... a = ['кіт', 'вікно', 'жбурляти']
>>> for x in a:
...     print x, len(x)
... 
кіт 3
вікно 5
жбурляти 8

Модифікація членів послідовності під час пербору небезпечна (це, власне, може трапитися лише зі змінюваними типами, як, скажімо, списки). Якщо потріно змінити список, що в даний час перебирається, (напр., подвоїти певні члени), то для цього слід перебирати копію списку. Нотація для позначення частин списку дозволяє це зручно робити:

>>> for x in a[:]: # зробити копію цілого списку за допомогою частинної нотації
...     if len(x) > 6: a.insert(0, x)
... 
>>> a
['жбурляти', 'кіт', 'вікно', 'жбурляти']

Функція range()

Якщо потрібно перебрати послідовність чисел, то тут стане в нагоді стандартна функція range(). Вона створює списки, що містять арифметичні прогресії:

>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Задана кінцева величина ніколи не є частиною створеного списку: range(10) створює список із десяти величин, що відповідає індексам послідовності довжиною 10 елементів. Можливо також задати початок списку іншим числом та вказати іншу величину зростання (навіть негативну; інколи це зветься "крок"):

>>> range(5, 10)
[5, 6, 7, 8, 9]
>>> range(0, 10, 3)
[0, 3, 6, 9]
>>> range(-10, -100, -30)
[-10, -40, -70]

Щоб перебрати індекси послідовності, слід використовувати функції range() та len() таким чином:

>>> a = ['У', 'Марічки', 'є', 'ягнятко']
>>> for i in range(len(a)):
...     print i, a[i]
... 
0 У
1 Марічки
2 є
3 ягнятко

Твердження break та continue; конструкція else у циклах

Твердження break, як і в C, перериває найближчий цикл for чи while.

Твердження continue, також запозичене з C, продовжує перебір з наступного кроку.

Циклічні твердження можуть також мати конструкцію else, яка виконується, коли цикл завершується виснаженням списку (для for) або коли умова перестає бути істинною (для while), але не тоді, коли цикл закінчується твердженням break. Наведений нижче код показує, як це діє на прикладі пошуку простих чисел:

>>> for n in range(2, 10):
...     for x in range(2, n):
...         if n % x == 0:
...             print n, 'дорівнює', x, '*', n/x
...             break
...     else:
...     # ми потрапили сюди, бо не знайшли спільного множника
...     print n, 'просте число'
... 
2 просте число
3 просте число
4 дорівнює 2 * 2
5 просте число
6 дорівнює 2 * 3
7 просте число
8 дорівнює 2 * 4
9 дорівнює 3 * 3

Твердження pass

Твердження pass не робить нічого. Воно використовується тоді, коли твердження потрібне синтаксично, але програма не потребує жодної дії. Наприклад:

>>> while True:
...     pass # Очікування сигналу переривання з клавіатури
...

Визначення функцій

Створімо функцію, що виводить числа Фібоначчі до певної межі:

>>> def fib(n): # вивести числа Фібоначчі до n
...     """Вивести числа Фібоначчі до n."""
...     a, b = 0, 1
...     while b < n:
...         print b,
...         a, b = b, a+b
... 
>>> # Тепер викликаємо щойно задану функцію:
... fib(2000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

Ключове слово def вводить визначення (дефініцію) функції. За ним повинна бути назва функції та оточений дужками список формальних параметрів. Твердження, що утворюють тіло функції починаються з наступного рядка і повинні бути  виділені пробілами. Першим, але необов'язковим, твердженням функції може бути буквальна величина, задана ланцюжком, яка коротко описує функцію.

Існують спеціальні утиліти, що використовують документаційні ланцюжки для автоматичного створення друкованої чи онлайн документації або для перегляду коду в діалоговому режимі. Документація коду - дуже гарна звичка, отож спробуйте не забувати про це.

Виконання функції вводить нову таблицю символів, що використовується для локальних змінних функції. Зокрема, всі присвоєння змінним всередині функції зберігають свої величини у локальній таблиці символів, тоді як при посиланні на змінну пошук починається у локальній, а потім продовжується у глобальній таблиці символів, і вже потім - у таблиці вмонтованих назв. Таким чином, глобальні змінні не можуть отримувати нові величини всередині функцій (за винятком якщо вони названі у твержденні global), хоча посилання на них можливе.

Параметри (або аргументи) функції вводяться в таблицю символів функції при її виклику. Отже аргументи передаються через виклик за величиною, де величина - завжди посилання на об'єкт, а не величина самого об'єкта. (Кращим визначенням було б виклик за посиланням на об'єкт, оскільки при передачі змінюваного об'єкта будь-які його зміни вседедині викликаної функції будуть видимі в середовищі, що викликало цю функцію, напр., додання нових елементів до списку). Коли одна функція викликає іншу, то створюється нова локальна таблиця символів для цього виклику.

Визначення функції додає назву функції до поточної символьної таблиці. Величина назви функції належить до певного типу, що розпізнається інтерпретатором як задана користувачем функція. Ця величина може присвоюватися іншій назві, що потім теж може використовуватися як функція. Тут показано, як діє загальний механізм перейменування:

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

Можливо хтось заперечить, що fib - не функція, а процедура. У Пітоні, як і в C, процедури - це функції, що не повертають жодної величини. Власне, з течнічної точки зору, процедури таки повертають певну величину, хоча й досить нецікаву. Ця величина зветься None (вбудована назва). Загалом, ця величина не виводиться інтерпретатором, якщо це єдина можлива величина для виводу. Але якщо дійсно хочеться її побачити, то це можна зробити таким чином:

>>> print fib(0)
None

Створення функції, що повертає список чисел Фібоначчі замість їх виведення, досить просте:

>>> def fib2(n): # повертає числа Фібоначчі до n
...     """Повертає список чисел Фібоначчі до n"""
...     result = []
...     a, b = 0, 1
...     while b < n:
...         result.append(b) # див. нижче
...         a, b = b, a+b
...     return result
... 
>>> f100 = fib2(100) # виклик функції
>>> f100 # виведення результату
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

Цей приклад також демонструє кілька нових рис мови Пітон:

  • Твердження return повертає певну величину із функції. Якщо return вжито без аргументів, то результатом повернення є None. Якщо процедура закінчується сама по собі, то результатом повернення є None.
  • Твердження result.append(b) викликає метод  спискового об'єкта result. Метод - це функція, що "належить" певному об'єкту. Вона викликається у формі  obj.назва_методу, де obj - певний об'єкт (може також бути виразом) і назва_методу - назва методу, визначеного  типом об'єкта. Різні типи визначають різні методи. Методи різних типів можуть мати однакову назву, не спричиняючи при цьому двозначності. (Ви можете також визначити і ваші власні методи за допомогою класів, про що йтиметься далі). Наведений у цьому прикладі метод append() визначений для спискових об'єктів; він додає новий елемент в кінець списку. У цьому прикладі це еквівалентно "result = result + [b]", але цей метод є більш ефективним.

Докладніше про визначення функцій

Можливо також визначити функцію зі змінною кількістю арґументів. Для цього існує три способи, що можуть сполучуватися.

Стандартні величини аргументів

Найкорисніший спосіб - це визначити типову величину для одного чи більше аргументів. Це створює можливість виклику функції з меншою кількістю аргументів, аніж задано у визначенні функції. Наприклад:

def ask_ok(prompt, retries=4, complaint='Так чи ні, будь-ласка!'):
     while True:
         ok = raw_input(prompt)
         if ok in ('т', 'та', 'так'): return True
         if ok in ('н', 'ні', 'нєт'): return False
         retries = retries - 1
         if retries < 0: raise IOError, 'затятий користувач'
         print complaint

Ця функція може викликатися як ask_ok('Справді закрити програму?') чи як ask_ok('Переписати файли?', 2).

Цей приклад також ілюструє ключове слово in, що дозволяє перевірити чи дана послідовність містить певну величину.

Стандартні величини обчислюються в момент задання функції відповідно до визначаючого контенсту, отже:

i = 5

def f(arg=i):
    print arg

i = 6
f()

виведе 5.

Важливе зауваження:

стандартна величина обчислюється лише раз. Певна відмінність існує коли стандартна величина - змінюваний об'єкт, скажімо, список, словник, реалізація більшості класів. Наприклад, наступна функція акумулює аргументи, що передаються при подальших викликах:

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

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

Це виведе:

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

Якщо ви не хочете, щоб стандартна величина поділялася між усіма настуними викликами, то можна створити функцію на зразок:

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

Ключові аргументи

Функції можуть також викликатися за допомогою ключових аргументів у вигляді "ключ = величина". Нприклад, ця функція:

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

може викликатися у будь-який вказаний нижче спосіб:

parrot(1000)
parrot(action = 'VOOOOOM', voltage = 1000000)
parrot('a thousand', state = 'pushing up the daisies')
parrot('a million', 'bereft of life', 'jump')

але такі виклики неправильні:

parrot() # пропущено обов'язковий аргумент
parrot(voltage=5.0, 'dead') # неключовий аргумент передається після ключового
parrot(110, voltage=220) # дві величини для одного аргумента
parrot(actor='John Cleese') # невідомий ключ

Взагалі у списку аргументів позиційні аргументи повинні бути розташовані перед ключовими, при цьому ключі повинні бути вибрані з формальних назв параметрів. Чи задані для цього параметра стандартні величини - неважливо. Жоден аргумент не може отримувати величину більш, ніж один раз - формальні назви параметрів, що відповдають позиційним аргументам не можуть використовуватися як ключові слова під час того самого виклику. Ось приклад того, коли помилка відбувається саме через це обмеження:

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

Якщо останній формальний параметр задано у формі **назва, то він отримує словник, що складається з аргументів, чиї ключі не відповідають формальним параметрам. Він може сполучуватися з формальним параметром у формі *назва (що описано в наступному підрозділі), який отримує кортеж, що складається з позиційних арґументів, не включених у список формальних переметрів (аргумент *назва повинен передувати аргументу **назва). Наприклад, функцію, задану таким чином:

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
    keys = keywords.keys()
    keys.sort()
    for kw in keys: print kw, ':', keywords[kw]

можна викликати отак:

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

Що, звичайно, виведе:

-- 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.
----------------------------------------
client : John Cleese
shopkeeper : Michael Palin
sketch : Cheese Shop Sketch

Зауважте, що метод ключових аргументів sort() викликано перед виведенням змісту словника keywords; бо інакше порядок виведення аргументів був би невизначеним.

Списки аргументів довільної довжини

Нарешті, остання часто використовувана можливість - визначення функції, що може бути викликана з будь-якою кількістю аргументів. Ці аргументи передаються за допомогою кортежа. Нуль чи більше звичайних аргументів можуть передувати змінній кількості аргументів.

def fprintf(file, format, *args):
    file.write(format % args)

Розпакування списків аргументів

Зворотня ситуація трапляється, коли аргументи задані списком чи кортежем, але їх потрібно розпакувати для виклику функції, що потребує окремих позиційних аргументів. Наприклад, вбудована функція range() потребує двох окремих аргументів, що вказують на межі послідовності. Якщо вони не задані окремо, виклик функції слід писати з оператором *, що дозволяє розпакувати аргументи, задані списком чи кортежем:

>>> range(3, 6) # звичайний виклик з окремими аргументами
[3, 4, 5]
>>> args = [3, 6]
>>> range(*args) # виклик із аргументами, розпакованими зі списку
[3, 4, 5]

Лямбда-форми

За популярною вимогою кілька нових рис, типових для функціональних мов програмування та мови Lisp, було додано до Пітона. Ключове слово lambda дозволяє створювати невеличкі анонімні функції. Ось, наприклад, функція, що повертає суму двох своїх аргументів: "lambda a, b: a+b". Лямбда-форми можуть стати в нагоді, коли потрібні об'єкти функцій. Синтаксично вони обмежені одним єдиним виразом. Семантично вони просто синтаксичний цукор у відношенні до нормального визначення функції. Подібно до вкладених функцій лямбда-форми можуть посилатися на змінні із зовнішнього контексту:

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

Документаційні ланцюжки

Починає формуватися певна конвенція щодо форматування документаційних ланцюжків.

Перший радок повинен містити стислу інформацію про мету об'єкта. Заради стислості в ньому не повинно бути назви чи типу об'єкта, бо їх можна отримати і в інший спосіб (за винятком коли назва є дієсловом, що описує дію функції). Цей рядок повинен починатися з великої літери і закінчуватися крапкою.

Якщо текст документації складається з кількох рядків, то другий рядок повинен бути пустим, що візуально відокремлює заголовок від решти опису. Наступні рядки повинні складатися з одного чи більше абзаців, що описують ньюанси виклику об'єкта, бічні ефекти тощо.

Парсер Пітона не відкидає початкові пробіли у багаторядкових буквальних величинах, виражених ланцюжками, отже утиліти, що обробляють документацію, при потребі повинні відкидати ці пробіли. Перший непустий рякок після заголовка визначає кількість пробілів для всього подальшого тексту документації. (Ми не можемо використовувати для цієї мети перший рядок, тому що він загалом розташований відразу після початкових лапок, а отже не виділений пробілами). Кількість початкових пробілів, "еквівалентна" знайденій у цьому рякду потім видаляється від початку усіх наступних рядків. Рядки з меншою кількістю пробілів не повинні зустрічатися, але якщо таки зустрічаються, то всі початкові пробіли повинні видалятися. Еквівалентність пробілів повинна перевірятися після розширення табуляторів (типово до восьми символів).

Ось приклад багаторядкового документаційного ланцюжка:

>>> def my_function():
...     """Не робить нічого, лише містить документацію.
... 
...     Справді, ця функція не робить нічого.
...     """
...     pass
... 
>>> print my_function.__doc__
Не робить нічого, лише містить документацію.

     Справді, ця функція не робить нічого.