Пориньте у Python 3/Стандартні типи даних

В основі будь-якої філософії лежить здивування, її розвитком є дослідження, її кінцем – незнання.
Мішель де Монтень

Типи даних. Давайте на хвилинку відкладемо в бік вашу першу програму, і поговоримо про типи даних. В Python кожне значення має тип, але ви не повинні оголошувати тип змінної. Як це працює? Покладаючись на початково присвоєне змінній значення, Python визначає якому типу воно належить і запам’ятовує його самостійно.

Python має багато вбудованих типів даних. Ось деякі найважливіші:

  1. Логічні (Булеві) змінні приймають значеня True або False.
  2. Числа можуть бути цілими (1 і 2), з десятковими дробами (1.1 і 1.2), звичайними дробами (1/2 and 2/3), чи навіть комплексними.
  3. Рядки є послідовностями символів Юнікоду (наприклад HTML документ)
  4. Байти та масиви байтів (наприклад зображення в форматі JPEG)
  5. Списки є впорядкованими послідовностями значень.
  6. Кортежі є впорядкованими незмінними послідовностями значень.
  7. Множини є невпорядкованими наборами значень.
  8. Словники є невпорядкованими наборами пар ключ-значення.

Звичайно існує ще багато типів крім вищеперелічених. В Python все - об’єкт, тому є такі типи як модуль, функція, клас, метод, файл і навіть відкомпільований код. Ви вже зустрічались з деякими з них: модулі мали імена, функції мали докстрінґи. Ви дізнаєтесь про класи в розділі Класи та ітератори, і про файли в розділі Файли.

Рядки і байти достатньо важливі і достатньо складні щоб присвятити їм окремий розділ. Давайте спершу розглянемо інші.


* * *


Булевий тип

ред.
У булевому контексті можна використовувати фактично будь-які вирази

Булеві змінні приймають лише істинне чи хибне значення. Python має для цих значень дві відповідні константи: True та False, які можна використовувати для присвоєння значень змінним. Окрім констант, булеві значення можуть приймати вирази. А в деяких місцях (наприклад, в операторі if) Python навіть очікує того, що значення виразу можна буде привести до булевого типу. Такі місця називаються булевими контекстами. Ви можете використати майже будь-який вираз в булевому контексті, і Python намагатиметься визначити його істинність. Різні типи даних мають різні правила щодо того, які значення є істинними, а які - хибними в булевому контексті. (Сенс останніх речень стане зрозумілішим, коли ви побачите деякі приклади далі в цьому розділі.)

Візьмімо для прикладу цей шматочок коду з humansize.py:

if size < 0:
    raise ValueError('number must be non-negative')

size - ціле число, 0 теж ціле, а < - оператор над числами. Результат виразу size < 0 завжди булевий. Ви можете переконатись в цьому самостійно в інтерактивній оболонці Python:

>>> size = 1
>>> size < 0
False
>>> size = 0
>>> size < 0
False
>>> size = -1
>>> size < 0
True

Від Python 2 була успадкована цікава особливість: за потреби вважати булеві значення числами. True це 1, а False - 0:

>>> True + True
2
>>> True - False
1
>>> True * False
0
>>> True / False
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: int division or modulo by zero

Ей, ей, ей! Ніколи не робіть так. Забудьте навіть, що я вам про таке розповідав. Серйозно.

Числа

ред.

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

>>> type(1) 
<class 'int'>

Функцію type() можна використовувати, щоб визначати тип значення змінної. Як можна було очікувати, 1 - ціле (int).

>>> isinstance(1, int)
True

Для перевірки приналежності типу можна використовувати функцію isinstance().

>>> 1 + 1
2

Додавання двох цілих дає нам цілий результат.

>>> 1 + 1.0
2.0
>>> type(2.0)
<class 'float'>

Результатом додавання цілого та дробового числа є дробове. Для виконання додавання Python приводить ціле число до типу float і повертає результат цього ж типу.

Перетворення цілих в дробові та навпаки

ред.

Щойно ви бачили, що деякі оператори (такі, як додавання) за потреби перетворюють цілі числа в дробові. Ви можете зробити це саме самостійно.

>>> float(2)
2.0

Можна явно перетворювати int у float, використовуючи функцію float()

>>> int(2.0) 
2

Нічого дивного і в тому, що ви можете зробити навпаки за допомогою функції int

>>> int(2.5) 
2

Функція int просто відкидає дробову частину, а не округлює.

>>> int(-2.5) 
-2

Функція int для від’ємних повертає найменше ціле число, більше або рівне даному (тобто теж просто відкидає дробову частину). Тому не плутайте її з функцією math.floor.

>>> 1.12345678901234567890
1.1234567890123457

Десяткові дроби мають точність до 15 знаків після коми.

>>> type(1000000000000000)
<class 'int'>

Цілі можуть бути як завгодно великими.

Python 2 має окремі типи int і long для цілих і довгої арифметики. Тип int був обмежений значенням sys.maxint, яке залежало від платформи (зазвичай  ). Python 3 має лише один цілий тип, який загалом поводиться так, як тип long в Python 2. Див. PEP 237 для деталей.

Основні числові операції

ред.

З числами можна робити все:

>>> 11 / 2      
5.5

Оператор / виконує ділення чисел з плаваючою крапкою. Він повертає float, навіть якщо чисельник та знаменник цілі.

>>> 11 // 2     
5

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

>>> 11 // 2    
6

При цілочисельному діленні від’ємних чисел оператор // округлює "вгору" до найближчого цілого. Хоча, говорячи формально, він округлує вниз, тому що -6 менше за -5.

>>> 11.0 // 2   
5.0

Оператор // не завжди повертає цілі. Якщо чисельник чи знаменник дробові, // повертає float, яке, щоправда, все одно округлене до найближчого цілого.

>>> 11 ** 2     
121

Оператор ** означає піднесення до степеня.  

>>> 11 % 2      
1

Оператор % повертає остачу від цілочисельного ділення.

У Python 2 оператор / зазвичай означав цілочисельне ділення (якщо застосовувався до цілих), але ви могли просто змусити його поводитись як дробове ділення, включаючи спеціальну директиву у свій код:

>>> from __future__ import division
>>> 1/2

У Python 3 оператор / завжди означає ділення чисел з плаваючою комою. Див. PEP 238 для деталей.


Звичайні дроби

ред.

Python не обмежується тільки цілими числами та десятковими дробами. Він також знає арифметику зі звичайними дробами, яку ви вивчили і одразу забули ще в школі.

>>> import fractions

Щоб почати використовувати звичайні дроби, потрібно імпортувати модуль fractions.

>>> x = fractions.Fraction(1, 3) 
>>> x
Fraction(1, 3)

Щоб створити дріб, використовують об’єкт типу Fraсtion і передають його конструктору чисельник та знаменник.

>>> x * 2
Fraction(2, 3)

З дробами можна виконувати звичайні математичні дії, результатом яких буде новий об’єкт Fraсtion.

>>> fractions.Fraction(6, 4) 
Fraction(3, 2)

Об’єкт Fraсtion автоматично скорочує дроби.

>>> fractions.Fraction(0, 0)
Traceback (most recent call last): 
  File "<stdin>", line 1, in <module> 
  File "fractions.py", line 96, in __new__ 
    raise ZeroDivisionError('Fraction(%s, 0)' % numerator)
ZeroDivisionError: Fraction(0, 0)

Python не дозволить створити дріб з нульовим знаменником.

Тригонометрія

ред.

В Python також доступні тригонометричні функції.

>>> import math 
>>> math.pi 
3.1415926535897931

Модуль math має константу для   - відношення довжини кола до його діаметру.

>>> math.sin(math.pi / 2)
1.0

math містить всі базові тригонометричні функції: sin(), cos(), tan() і навіть такі, як asin().

>>> math.tan(math.pi / 4)
0.99999999999999989

Щоправда, варто зауважити, що Python не дає абсолютної точності. tan(π / 4) мусить повертати 1.0, а не 0.99999999999999989.

Числа в булевому контексті

ред.
Нульові значення вважаються хибними, ненульові - істинними

Ви можете використовувати числа в булевому контексті, такому, як умова в операторі if. Нульові значення вважаються хибними, ненульові - істинними.

>>> def is_it_true(anything): 
...   if anything: 
...     print("yes, it's true")
...   else: 
...     print("no, it's false")
...

Ви знали, що можна описувати свої функції прямо в інтерактивній сесії Python? Просто натискайте Enter в кінці кожного рядка і додайте один порожній рядок в кінці функції.

>>> is_it_true(1)
 yes, it's true 
>>> is_it_true(-1)
 yes, it's true 
>>> is_it_true(0)
 no, it's false

У булевому контексті всі цілі, окрім нуля, дорівнюють True, 0 - False.

>>> is_it_true(0.1)
 yes, it's true 
>>> is_it_true(0.0)
 no, it's false

Ненульові десяткові дроби - True, 0.0 - False. Будьте обережними з цим! Найменша похибка округлення (цілком ймовірна річ, як ви могли побачити в попередньому розділі) - і Python буде перевіряти 0.0000000000001 замість 0.0 та отримає значення True.

>>> import fractions 
>>> is_it_true(fractions.Fraction(1, 2))
 yes, it's true 
>>> is_it_true(fractions.Fraction(0, 1))
 no, it's false

Звичайні дроби теж можна використати в булевому контексті. Fraction(0, n) - хибне для будь-якого n. Усі інші дроби істинні.


* * *


Списки

ред.

Списки - це робоча кобилка мови Python. Коли я кажу "список", ви можете подумати "масив, розмір якого я повинен задати наперед і який мусить містити елементи одного типу і т.д.". Не думайте так. Списки набагато крутіші за масиви.

Список у Python - це як масив у Perl 5. У Perl 5 змінні, які містять масиви, завжди починаються з символу @. У Python змінні можна називати як завгодно, а Python сам визначить тип.

Список у Python - це набагато більше, ніж масив в Java (хоча й може використовуватись як такий, якщо це все, що вам потрібно від життя). Кращим аналогом міг би бути клас ArrayList, який може містити довільні об’єкти і динамічно розширюватись при додаванні нових елементів.

Створення списку

ред.

Створити список дуже просто - помістіть список значень, розділених комою, в квадратні дужки:

>>> a_list = ['a', 'b', 'mpilgrim', 'z', 'example'] 
>>> a_list
['a', 'b', 'mpilgrim', 'z', 'example']

Створюємо список з п’яти елементів. Їхній порядок зберігається, що не є випадковим, бо список - впорядкований набір елементів.

>>> a_list[0]
'a'

Нумерація елементів списку починається з нуля. Першим елементом непорожнього списку a_list є a_list[0].

>>> a_list[4]
'example'

Останнім елементом списку a_list з п’яти елементів є a_list[4]. Тому що нумерація починається з нуля.

>>> a_list[-1]
'example'

Від’ємні індекси в масиві задають відповідні по порядку елементи, якщо рахувати справа наліво. Останнім елементом непорожнього списку a_list завжди є a_list[-1].

>>> a_list[-3]
'mpilgrim'

Якщо від’ємні індекси для вас трохи заплутані, думайте про них так: a_list[-n] == a_list[len(a_list) - n]. Тому в нашому випадку: a_list[-3] == a_list[5 - 3] == a_list[2].

Зрізання списків

ред.
a_list[0] - перший елемент a_list.

Щойно створивши список, ви можете отримати будь-яку його частину як новий список. Ця частина називатиметься зрізом.

>>> a_list
['a', 'b', 'mpilgrim', 'z', 'example'] 
>>> a_list[1:3]
['b', 'mpilgrim']

Ви можете отримати зріз списку, задавши два індекси. Отримане значення - новий список, який містить всі елементи списку по порядку, починаючи від першого індексу (тут a_list[1]) і аж до другого (тут a_list[3]), але не включаючи його.

>>> a_list[1:-1]
['b', 'mpilgrim', 'z']

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


>>> a_list[0:3]
['a', 'b', 'mpilgrim']

Нумерація елементів в списку починається з нуля, тому a_list[0:3] поверне перші три елементи списку, починаючи з a_list[0] аж до a_list[3] невключно.

>>> a_list[:3]
['a', 'b', 'mpilgrim']

Якщо лівий індекс зрізу дорівнює нулю, його можна не вказувати. Тому a_list[:3] - це те саме, що й a_list[0:3].

>>> a_list[3:]
['z', 'example']

Аналогічно, якщо правий індекс - довжина списку, його теж можна не вказувати. Тому a_list[3:] - такий самий, як a_list[3:5], бо цей список має п’ять елементів. Тут є приємна симетрія. В даному п’ятиелементному списку a_list[:3] повертає перші три елементи, а a_list[3:] - останні два. Взагалі, a_list[:n] поверне перших n елементів, а a_list[n:] - решту, незалежно від довжини списку. (Якщо n - більше чи рівне довжини списку, всі елементи попадуть в перший зріз, а решта відповідно буде порожньою.)

>>> a_list[:]
['a', 'b', 'mpilgrim', 'z', 'example']

Якщо пропустити обидва індекси, всі елементи списку будуть включені до зрізу. Але це те саме, що й оригінальна змінна a_list. Це новий список, який містить ті самі елементи, що й оригінал. a_list[:] - скорочення для створення копії списку.

Додавання елементів до списку

ред.

Існує чотири способи додавання елементів в список.

>>> a_list = ['a'] 
>>> a_list = a_list + [2.0, 3]

Оператор + конкатенує два списки, щоб утворити новий. Список може містити довільну кількість елементів (обмежень на розмір списку немає, окрім, звісно, об’єму доступної пам’яті). Проте ви мусите пам’ятати, що конкатенація списків створює додатковий список в пам’яті. В даному випадку він негайно присвоюється існуючій змінній a_list. Тому цей рядок коду насправді працює в два етапи: створення нового списку, і потім присвоєння. Якщо список довгий, то в проміжку між етапами програма може потребувати досить великі об’єми пам’яті.

>>> a_list 
['a', 2.0, 3]

Список може містити елементи будь-яких типів, і елементи в списку не мусять бути одного типу. Тут ми маємо список, що складається з рядка, числа з плаваючою крапкою і цілого числа.

>>> a_list.append(True) 
>>> a_list 
['a', 2.0, 3, True]

Метод append() додає один елемент до кінця списку. (Тепер в списку вже чотири різні типи даних!)

>>> a_list.extend(['four', 'Ω']) 
>>> a_list 
['a', 2.0, 3, True, 'four', 'Ω']

Списки реалізовані як класи. Створення списку - це насправді ініціалізація екземпляру класу. І, так само як й інші класи, списки мають методи, щоб оперувати на них. Метод extend() приймає один аргумент - список, і додає всі елементи цього списку до початкового.

>>> a_list.insert(0, 'Ω') 
>>> a_list 
['Ω', 'a', 2.0, 3, True, 'four', 'Ω']

Метод insert() вставляє один елемент в список. Його перший аргумент - індекс першого аргументу в списку, який буде зсунутий зі своєї позиції. Елементи списку не мусять бути унікальними; наприклад, зараз список містить два різні елементи зі значенням 'Ω': перший елемент (a_list[0]) і останній елемент (a_list[6]).

Метод a_list.insert(0, value) схожий на функцію unshift() в Perl. Він додає елемент на початок списку, і всі інші елементи змінюють свій індекс, щоб звільнити місце.

Давайте детальніше розглянемо різницю між append() та extend().

>>> a_list = ['a', 'b', 'c'] 
>>> a_list.extend(['d', 'e', 'f']) 
>>> a_list 
['a', 'b', 'c', 'd', 'e', 'f']

Метод extend() отримує один аргумент, який завжди мусить бути списком, і додає кожен з елементів аргументу в a_list.

>>> len(a_list) 
6 
>>> a_list[-1]
'f'

Якщо для списку з трьох елементів викликати метод extend(), якому теж передати список з трьох елементів, то в результаті отримаємо шестиелементний список.

>>> a_list.append(['g', 'h', 'i']) 
>>> a_list
['a', 'b', 'c', 'd', 'e', 'f', ['g', 'h', 'i']]

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

>>> len(a_list) 
7 
>>> a_list[-1] 
['g', 'h', 'i']

Якщо для списку з шести елементів викликати метод append() і передати йому список, ми отримаємо список з семи елементів. Чому семи? Тому що останній елемент, незважаючи на те, що є списком, все одно буде одним елементом. Списки можуть містити будь-які дані, в тому числі й інші списки.

Пошук значень в списку

ред.
>>> a_list = ['a', 'b', 'new', 'mpilgrim', 'new'] 
>>> a_list.count('new') 
2

Як і можна було очікувати, метод count() (підрахуй) повертає кількість входжень певного елемента в список.

>>> 'new' in a_list 
True 
>>> 'c' in a_list 
False

А якщо вам лише потрібно знати чи є якийсь елемент в списку, чи його немає, оператор in буде дещо швидшим. Оператор in завжди повертає True або False, він не рахує скільки разів елемент входить в список.

>>> a_list.index('mpilgrim') 
3

Ні оператор in, ні метод count() не говорять вам, де саме в списку перебуває елемент. Якщо вам потрібна така інформація - зверніться до методу index(). За замовчуваням він шукає по всьому списку, хоча ви можете передати необов’язковий другий елемент - індекс, з якого починати пошук, і навіть необов’язковий третій аргумент - індекс, на якому закінчувати пошук.

>>> a_list.index('new') 
2

Метод index() знаходить місце першого входження елемента в список. У даному випадку, 'new' двічі зустрічається в списку, в елементах a_list[2] та a_list[4], але index() повертає лише позицію першого входження.

>>> a_list.index('c') 
Traceback (innermost last):
 File "<interactive input>", line 1, in ? 
ValueError: list.index(x): x not in list

Ви могли не очікувати такого, але якщо значення не знаходиться в списку, метод index() згенерує виняток.

Стривайте, що? Все правильно: якщо index() не знаходить значення в списку, він генерує виняток. Це значно відрізняється від поведінки аналогічних методів ув інших мовах, які б повертали якийсь недійсний індекс, наприклад -1. І хоча це спершу здається незручним, думаю, з часом ви це оціните. Це означає, що у вашій програмі відбудеться збій прямо в місці виникнення проблеми, а не триватиме робота з неправильними даними, що в майбутньому призведе до дивних незрозумілих помилок. Пам’ятаєте --1 - це допустимий індекс. Якщо метод index() поверне -1, це призведе до не надто веселих сеансів зневадження.


Видалення елементів зі списку

ред.
Списки ніколи не містять порожнин

Списки можуть розширюватись та скорочуватись автоматично. Ви вже бачити частину з доповненням списку. Так сам є кілька різних способів вилучення елементів зі списку.

>>> a_list = ['a', 'b', 'new', 'mpilgrim', 'new'] 
>>> a_list[1] 
'b' 
>>> del a_list[1] 
>>> a_list ['a', 'new', 'mpilgrim', 'new']

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

>>> a_list[1]
'new'

Спроба отримати елемент з індексом 1 після видалення елемента з таким індексом не спричинює помилку. Всі елементи, які перебували в списку після видаленого, зсувають свій індекс, щоб "заповнити порожнину", яка виникла в результаті видалення елемента.

Не знаєте індекс елемента? Не проблема, їх можна видаляти знаючи значення.

>>> a_list.remove('new') 
>>> a_list
['a', 'mpilgrim', 'new']

Елементи можна також видаляти зі списку за допомогою методу remove(). Метод remove() отримує значення і видаляє перше входження елемента з таким значенням зі списку. Знову ж таки, всі елементи після видаленого зсунуться, щоб заповнити порожнину. Списки ніколи не містять порожнин.

>>> a_list.remove('new') 
>>> a_list
['a', 'mpilgrim'] 
>>> a_list.remove('new')
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list

Метод remove() можна викликати так часто, як вам захочеться, проте він буде генерувати винятки при спробах видалити елемент, відсутній у списку.

Видалення елементів зі списку: бонусний раунд

ред.

Іншим цікавим методом списку є pop(). Метод pop() - це просто ще один спосіб видалити елемент зі списку, але з викрутасом.

>>> a_list = ['a', 'b', 'new', 'mpilgrim'] 
>>> a_list.pop()
'mpilgrim' 
>>> a_list
['a', 'b', 'new']

При виклику без аргументів pop() видаляє останній елемент зі списку і повертає його значення.

>>> a_list.pop(1)
'b' 
>>> a_list
['a', 'new'] 
>>> a_list.pop()
'new' 
>>> a_list.pop()
'a'

Ви можете витягувати зі списку довільні елементи. Просто передавайте в pop() їхній індекс, і він самостійно видалить їх, зсуне індекси всіх інших і поверне нам значення.

>>> a_list.pop()
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
IndexError: pop from empty list

Виклик pop() для порожнього списку генерує виняток.

Виклик методу pop() без аргументів подібний функції pop() в Perl. Він видаляє останній елемент зі списку і повертає значення видаленого елементу. Perl містить іншу функцію shift(), яка видаляє перший елемент і повертає його значення. В Python це еківалентно виклику pop(0).

Списки в булевому контексті

ред.
Порожні списки хибні в булевому контексті; всі інші списки істинні.

Списки також можна використовувати в булевих контекстах таких, як, наприклад умова в if.

>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
... 
>>> is_it_true([])
no, it's false

У булевому контексті будь-який порожній список хибний.

>>> is_it_true(['a'])
yes, it's true

Будь-який список, який містить хоча б один елемент, істинний у булевому контексті.

>>> is_it_true([False])
yes, it's true

Незалежно від значення елементів, які він містить, непорожній список - істинний.


* * *


Кортежі

ред.

Кортеж - це незмінюваний список. Він завжди залишається таким, яким його створили.

>>> a_tuple = ("a", "b", "mpilgrim", "z", "example") 
>>> a_tuple
('a', 'b', 'mpilgrim', 'z', 'example')

Кортеж записується майже так само, як список, але замість квадратних дужок використовуються круглі.

>>> a_tuple[0]
'a'

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

>>> a_tuple[-1]
'example'

Від’ємні індекси відраховуються від кінця кортежу, так само, як і в списках.

>>> a_tuple[1:3]
('b', 'mpilgrim')

Зрізи теж працюють. Коли ви робите зріз списку - отримуєте новий список, робите зріз кортежу - отримуєте новий кортеж.

Основна відмінність між списками та кортежами: кортежі не можна змінювати. Технічно - вони незмінні. Практично - вони не мають методів, які б дозволили вам змінити їх. Списки містять такі методи: append(), extend(), insert(), remove() і pop(). Кортежі не містять таких методів. Ми можемо зробити зріз кортежу (тому що це створює новий кортеж) і перевірити, чи кортеж містить певне значення (тому що це не змінює кортеж), і ... на цьому все.

>>> a_tuple
('a', 'b', 'mpilgrim', 'z', 'example')
>>> a_tuple.append("new")               
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
AttributeError: 'tuple' object has no attribute 'append'

Доповнювати: не можна.

>>> a_tuple.remove("z")                 
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
AttributeError: 'tuple' object has no attribute 'remove'

Вилучати: не можна.

>>> a_tuple.index("example")            
4

Можна шукати елементи, бо це не змінює кортеж.

>>> "z" in a_tuple                      
True

Ну і можна перевіряти, чи елемент міститься в кортежі.

Тоді навіщо вони потрібні?

  • Кортежі працюють швидше, ніж списки. Якщо вам потрібен незмінний набір значень і все що ви будете робити - ітерувати по ньому, то використовуйте кортеж замість списку.
  • Ваш код стане безпечнішим, якщо ви захистите від запису дані, які не мусять змінюватись.
  • Деякі кортежі можуть використовуватись як ключі в словнику (кажучи конкретно - кортежі, які містять лише незмінні елементи, тобто числа, рядки чи інші кортежі). Списки ніколи не можна використовувати як ключі словника, тому що списки можуть змінюватись.

Кортежі можна перетворювати в списки, і навпаки. Вбудована функція tuple() отримує список і повертає кортеж з такими ж елементами, а функція list() отримує кортеж і повертає список. tuple() ніби "заморожує" список, а list() "розморожує" кортеж.

Кортежі в булевому контексті

ред.
>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
... 
>>> is_it_true(())
no, it's false

У булевому контексті порожній кортеж еквівалентний False.

>>> is_it_true(('a', 'b'))
yes, it's true

Будь-який кортеж, що містить хоча б один елемент, - істинний.

>>> is_it_true((False,))
yes, it's true

Будь-який непорожній кортеж - істинний, незалежно від значень його елементів. Але ви помітили кому?

>>> type((False))
<class 'bool'> 
>>> type((False,))
<class 'tuple'>

Щоб створити кортеж з одного елемента, ви мусите написати після нього кому. Якщо коми немає, Python думає, що ви просто взяли вираз в дужки, що не матиме жодного ефекту.

Присвоєння кількох значень за раз

ред.

Кортежі дозволяють робити класний трюк: присвоювати по кілька значень за раз:

>>> v = ('a', 2, True) 
>>> (x, y, z) = v 
>>> x
'a' 
>>> y
2 
>>> z
True

v - кортеж з трьох елементів, а (x, y, z) - кортеж з трьох змінних. Присвоєння одного іншому присвоює кожній змінній кортежу значення з v.

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

>>> (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)

Вбудована функція range() створює послідовність цілих чисел. (Правду кажучи, вона повертає ітератор, а не список чи кортеж, але ми розглянемо це детальніше потім.) MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY - змінні, які ми оголошуємо. (Цей приклад взятий з модуля calendar, маленького цікавого модуля, який друкує календарі, аналогічно програмі cal в Unix. Цей модуль містить константи для всіх днів тижня.)

>>> MONDAY
0 
>>> TUESDAY
1 
>>> SUNDAY
6

Тепер кожна змінна має своє значення: MONDAY = 0, TUESDAY = 1 і так далі.

Також можна використовувати присвоєння багатьох значень для створення функцій, які повертають кілька значень, повертаючи кортеж з ними. Той, хто викликав функцію, може зберігати результат як єдиний кортеж, а може присвоїти його значення окремим змінним. Багато стандартних бібліотек мови Python містять такі функції. Однією з них є модуль os, про який ви дізнаєтесь в наступному розділі.


* * *


Множини

ред.

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

Створення множини

ред.

Почнемо за порядком. Створити множину просто.

>>> a_set = {1} 
>>> a_set
{1}

Щоб створити множину з одного елемента, помістіть цей елемент у фігурні дужки.

>>> type(a_set)
<class 'set'>

Множини, як і всі інші типи в Python, реалізовані як класи. Але про це пізніше.

>>> a_set = {1, 2} 
>>> a_set
{1, 2}

Щоб створити множину з кількох елементів, перелічіть їх у фігурних дужках через кому.

Множину також можна створити зі списку.

>>> a_list = ['a', 'b', 'mpilgrim', True, False, 42] 
>>> a_set = set(a_list)

Щоб створити множину зі списку, використайте функцію set(). (Педанти, які знають про те, як реалізовані множини, вкажуть, що це насправді не виклик функції, а створення екземпляру класу. Я обіцяю, що далі в цій книжці ви дізнаєтесь про різницю. А поки що просто знайте, що set() поводить себе як функція, що повертає множину.)

>>> a_set
{'a', False, 'b', True, 'mpilgrim', 42}

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

>>> a_list
['a', 'b', 'mpilgrim', True, False, 42]

Початковий список не змінюється.

Не маєте ніяких значень взагалі? Не проблема! Можна створити порожню множину.

>>> a_set = set()

Щоб створити порожню множину, викличте set() без параметрів.

>>> a_set
set()

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

>>> type(a_set)
<class 'set'>

Незважаючи на те, що вона дивно виглядає, порожня множина - це все одно множина.

>>> len(a_set)
0

Яка не містить елементів.

>>> not_sure = {} 
>>> type(not_sure)
<class 'dict'>

З історичних причин дві фігурні дужки позначають не порожню множину, а порожній словник.

Зміна множини

ред.

Існує два способи додавати елементи до множини: метод add() і метод update().

>>> a_set = {1, 2} 
>>> a_set.add(4) 
>>> a_set
{1, 2, 4}

Метод add() приймає один аргумент будь-якого незмінного типу і додає його в множину.

>>> len(a_set)
3

Тепер множина містить три елементи.

>>> a_set.add(1) 
>>> a_set
{1, 2, 4} 
>>> len(a_set)
3

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

>>> a_set = {1, 2, 3} 
>>> a_set
{1, 2, 3} 
>>> a_set.update({2, 4, 6})

Метод update() приймає множину і додає всі елементи цієї множини до нашої. Це те ж саме, що викликати add() для кожного елемента множини-параметра.

>>> a_set
{1, 2, 3, 4, 6}

Значення, які вже були в множині, ігноруються, бо множини не містять дублікатів.

>>> a_set.update({3, 6, 9}, {1, 2, 3, 5, 8, 13}) 
>>> a_set
{1, 2, 3, 4, 5, 6, 8, 9, 13}

Також update() можна викликати з довільним числом параметрів, це те ж саме, що викликати цей метод послідовно для кожного з них окремо.

>>> a_set.update([10, 20, 30]) 
>>> a_set
{1, 2, 3, 4, 5, 6, 8, 9, 10, 13, 20, 30}

Окрім множин, update() може приймати багато інших типів, наприклад, списки. Для них він поводиться аналогічно - додає кожен їх елемент в множину.

Видалення елементів з множини

ред.

Є три способи видаляти елементи з множини. Перші два - discard() і remove() - мають одну тонку відмінність.

>>> a_set = {1, 3, 6, 10, 15, 21, 28, 36, 45} 
>>> a_set
{1, 3, 36, 6, 10, 45, 15, 21, 28} 
>>> a_set.discard(10) 
>>> a_set
{1, 3, 36, 6, 45, 15, 21, 28}

Метод discard() приймає єдине значення як параметр, і видаляє це значення з множини.

>>> a_set.discard(10) 
>>> a_set
{1, 3, 36, 6, 45, 15, 21, 28}

Якщо викликати discard() для значення, якого немає в множині, в ній нічого не зміниться. Не згенерується жодного винятку.

>>> a_set.remove(21) 
>>> a_set
{1, 3, 36, 6, 45, 15, 28}

Метод remove() також приймає єдине значення як аргумент, і також видаляє це значення з множини.

>>> a_set.remove(21)
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
KeyError: 21

А ось і тонка відмінність. Якщо значення немає в множині, метод remove() кидає виняток KeyError.

Як і списки, множини мають метод pop().

>>> a_set = {1, 3, 6, 10, 15, 21, 28, 36, 45} 
>>> a_set.pop()
1 
>>> a_set.pop()
3 
>>> a_set.pop()
36 
>>> a_set
{6, 10, 45, 15, 21, 28}

Метод pop() видаляє одне значення з множини і повертає його. Щоправда, оскільки множини - це невпорядковані набори, то "останнього" немає елемента - важко передбачити, який елемент буде повернуто.

>>> a_set.clear() 
>>> a_set
set()

Метод clear() видаляє всі значення множини, роблячи її порожньою. Це дорівнює присвоєнню a_set = set(), яке просто створить нову порожню множину і замінить посилання на неї в змінній.

>>> a_set.pop()
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
KeyError: 'pop from an empty set'

Спроба виклику pop() для порожньої множини створить виняток KeyError.

Теоретико-множинні операції

ред.

Розділ названо так тому, що зараз ми розглянемо ті дії з множинами, які вивчає відповідний розділ математики.

>>> a_set = {2, 4, 5, 9, 12, 21, 30, 51, 76, 127, 195} 
>>> 30 in a_set
True 
>>> 31 in a_set
False

Щоб визначити, чи належить множині елемент , використовуйте оператор in. Працює так само, як і зі списками.

>>> b_set = {1, 2, 3, 5, 6, 8, 9, 12, 15, 17, 18, 21} 
>>> a_set.union(b_set)
{1, 2, 195, 4, 5, 6, 8, 12, 76, 15, 17, 18, 3, 21, 30, 51, 9, 127}

Метод union() (об’єднання) повертає множину, що складається з елементів, які належать хоча б одній з двох множин.

>>> a_set.intersection(b_set)
{9, 2, 12, 5, 21}

Метод intersection() (перетин) повертає множину, що складається з елементів, які належать одночасно двом множинам.

>>> a_set.difference(b_set)
{195, 4, 76, 51, 30, 127}

Метод difference() (різниця) повертає множину з тих елементів a_list, які не належать b_list.

>>> a_set.symmetric_difference(b_set)
{1, 3, 4, 6, 8, 76, 15, 17, 18, 195, 127, 30, 51}

Метод symmetric_difference (симетрична різниця) повертає множину з тих елементів, які належать рівно одній з множин.

Троє з цих методів симетричні.

>>> b_set.symmetric_difference(a_set)                                       
{3, 1, 195, 4, 6, 8, 76, 15, 17, 18, 51, 30, 127}

Симетрична різниця між a_set і b_set може відрізнятись від симетричної різниці між ними ж у протилежному порядку, але пам’ятайте, що множини не є впорядкованими. Будь-які дві множини, що містять однаковий набір значень, вважаються рівними.

>>> b_set.symmetric_difference(a_set) == a_set.symmetric_difference(b_set)  
True

Що власне й доводить порівняння вище. Не вводьтесь ув оману друкованим представленням множин оболонкою Python. Вони містять однакові значення, тому є рівними.

>>> b_set.union(a_set) == a_set.union(b_set)                                
True

Об’єднання двох множин теж симетричне.

>>> b_set.intersection(a_set) == a_set.intersection(b_set)                  
True

І перетин множин симетричний.

>>> b_set.difference(a_set) == a_set.difference(b_set)                      
False

А різниця - ні. І це логічно, бо різниця множин аналогічна відніманню чисел, тож порядок операндів має значення.

І нарешті, є кілька запитань про множини, на які можна отримати відповідь:

>>> a_set = {1, 2, 3} 
>>> b_set = {1, 2, 3, 4} 
>>> a_set.issubset(b_set)
True

a_set - підмножина (subset) множини b_set — всі елементи a_set також належать b_set.

>>> b_set.issuperset(a_set)
True

Таке саме запитання, лишень навпаки. b_set - надмножина a_set, тому що всі члени a_set також є членами b_set.

>>> a_set.add(5) 
>>> a_set.issubset(b_set)
False 
>>> b_set.issuperset(a_set)
False

Як тільки ви додасте до a_set значення, що не міститься в b_set, обидві перевірки повернуть False.

Множини в булевому контексті

ред.

Тут, як завжди, все просто:

>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
... 
>>> is_it_true(set())
no, it's false 
>>> is_it_true({'a'})
yes, it's true 
>>> is_it_true({False})
yes, it's true

Порожня множина - False, а непорожня, не залежно від вмісту елементів - True.


* * *


Словники

ред.

Словники - це невпорядковані набори пар "ключ - значення". Коли ви додаєте до словника новий ключ, завжди також повинні додати туди і значення (яке можна буде змінити пізніше). Вони оптимізовані для отримання значення, якщо ви знаєте відповідний ключ, але ніяк не навпаки.

Словник у Python - це як Perl 5. У Perl 5 змінні, що зберігають хеші, завжди починаються з символу %. У мові Python змінні можна називати довільним чином, Python запам’ятає їхній тип самостійно.

Створення словника

ред.

Створити словник нескладно. Синтаксис подібний до синтаксису множин, але замість значень - пари "ключ-значення". Щойно ви отримали словник, можете перевіряти значення за їхніми ключами.

>>> a_dict = {'server': 'db.diveintopython3.org', 'database': 'mysql'}
>>> a_dict
{'server': 'db.diveintopython3.org', 'database': 'mysql'}

Спершу створюємо новий словник з двома елементами і присвоюємо їхні значення змінній a_dict. Кожен елемент - це пара "ключ-значення", і вся множина з елементів поміщена в фігурні дужки.

>>> a_dict['server']
'db.diveintopython3.org'

'server' - це ключ і пов'язане з ним значення, яке іменується як a_dict['server'] - 'db.diveintopython3.org'.

>>> a_dict['database']
'mysql'

'database' - ключ, який пов'язаний зі значенням 'mysql'.

>>> a_dict['db.diveintopython3.org']
Traceback (most recent call last):
 File "<stdin>", line 1, in <module> 
KeyError: 'db.diveintopython3.org'

Можна отримати значення за ключем, але не можна отримувати ключі за значенням. Тому a_dict['server'] дорівнює 'db.diveintopython3.org', а a_dict['db.diveintopython3.org'] генерує винятки, бо 'db.diveintopython3.org' - не ключ.

Модифікація словника

ред.

Словники не мають попередньо заданого обмеження в розмірі. Ви в будь-який час можете додавати до словника нові пари "ключ-значення" чи змінювати значення чинної пари. Розвиваючи попередній приклад:

>>> a_dict
{'server': 'db.diveintopython3.org', 'database': 'mysql'} 
>>> a_dict['database'] = 'blog' 
>>> a_dict
{'server': 'db.diveintopython3.org', 'database': 'blog'}

Словник не може містити дублікатів ключів. Присвоєння значення чинному ключу затре старе значення.

>>> a_dict['user'] = 'mark'

Нові пари можна додавати коли завгодно. Синтаксис аналогічний синтаксису модифікації чинної пари.

>>> a_dict
{'server': 'db.diveintopython3.org', 'user': 'mark', 'database': 'blog'}

Новий елемент (ключ 'user' і значення 'mark') з'являється всередині. Насправді те, що елементи були впорядковані в першому прикладі, - лише випадковість, як і те, що зараз вони не впорядковані.

>>> a_dict['user'] = 'dora'
>>> a_dict
{'server': 'db.diveintopython3.org', 'user': 'dora', 'database': 'blog'}

Присвоєння значення чинному ключу просто замінює старе на нове.

>>> a_dict['User'] = 'mark'
>>> a_dict
{'User': 'mark', 'server': 'db.diveintopython3.org', 'user': 'dora', 'database': 'blog'}

Чи замінить це значення ключа 'user' назад на "mark"? Ні! Придивіться до ключа уважніше - він починається з великої літери. Ключі словника чутливі до регістру, тому ця інструкція створить нову пару "ключ-значення", а не перепише чинну. Вони може бути схожими для вас, але для Python вони цілковито різні.

Словники змішаних значень

ред.

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

Насправді ви вже бачили словник з нерядковими ключами і значеннями в вашій першій програмі.

SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
            1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}

Давайте розберемо це в інтерактивній оболонці.

>>> SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
...             1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}
>>> len(SUFFIXES)
2

Як і для списків з множинами, функція len() повертає кількість елементів у словнику.

>>> 1000 in SUFFIXES
True

І як і в списках та множинах, ви можете використовувати оператор in для того, щоб з'ясувати, чи перебуває певний ключ у словнику.

>>> SUFFIXES[1000]
['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']

1000 - це ключ в словнику SUFFIXES. Його значення - список з восьми елементів.

>>> SUFFIXES[1024]
['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']

Так само з ключем 1024.

>>> SUFFIXES[1000][3]
'TB'

Оскільки SUFFIXES[1000] - список, ми можемо звернутись до окремого елемента списку за його індексом.

Словники в булевому контексті

ред.
Порожні словники хибні, всі інші - істинні

Словники теж можна використовувати в булевому контексті.

>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true({})
no, it's false

Порожній словник завжди дорівнює False в булевому контексті.

>>> is_it_true({'a': 1})
yes, it's true

Будь-який словник, що містить хоча б один елемент, дорівнює True в булевому контексті.


* * *


None - спеціальна константа мови Python. Це значення, що позначає відсутність значення. None - це не те ж саме що й False. None не 0. При порівнянні з None будь-що, окрім None, завжди поверне False.

None - єдине значення свого власного типу NoneType. Ви можете присвоїти значення None будь-яку змінну, але не можете створити інші об'єкти класу NoneType. Всі змінні зі значенням None дорівнюють одна одній.

>>> type(None)
<class 'NoneType'>
>>> None == False
False
>>> None == 0
False
>>> None == ''
False
>>> None == None
True
>>> x = None
>>> x == None
True
>>> y = None
>>> x == y
True

None в булевому контексті

ред.

У булевому контексті None еквівалентно False, а not None - True.

>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true(None)
no, it's false
>>> is_it_true(not None)
yes, it's true

Для подальшого читання

ред.


Ваша перша програма · Вирази над структурами