Към съдържанието

3. Lists (основи), tuples и sets ​

Занятие 3упражнения: 03-lists-tuples-sets/ в homework repositoryпримерните данни

Днес ще се научите да:

  • пазите много стойности в един list и да ги четете по индекс (от 0!) и със slices;
  • използвате операциите, общи за всички поредици: +, *, in, len, min, max;
  • променяте list с неговите методи и да го сортирате;
  • разбирате защо две имена могат да показват един и същ list - и как да направите копие;
  • използвате tuples като записи, които не могат да се променят, и да ги разопаковате;
  • използвате range за поредици от числа;
  • намирате уникалните стойности със sets и да сравнявате групи като UNION, INTERSECT и EXCEPT в SQL - по начина на Python.

От SQL към Python - един и същ въпрос, по два начина. Кои клиенти нямат кредити?

sql
SELECT client_id FROM clients
EXCEPT
SELECT client_id FROM loans;
python
all_clients = set()
with open("data/clients.csv", encoding="utf-8") as file:
    file.readline()                                  # skip the header
    for line in file:
        all_clients.add(line.split(",")[0])          # the 1st column: client_id

with_loans = set()
with open("data/loans.csv", encoding="utf-8") as file:
    file.readline()
    for line in file:
        with_loans.add(line.split(",")[1])           # the 2nd column: client_id

without_loans = all_clients - with_loans             # EXCEPT
print(len(all_clients), "clients,", len(with_loans), "with loans")
print(sorted(without_loans))
Променете кода и го изпълнете отново

1. Поредици ​

Какво е поредица (sequence)? ​

В занятие 2 използвахме for върху strings, lists и ranges. Всички те са поредици (sequences): контейнери, които пазят подредена колекция от стойности. Основните типове поредици:

ТипПримерМоже ли да се променя?
list["mortgage", "consumer", "overdraft"]да
tuple("L000001", "investment", 1044231.30)не
rangerange(1, 13)не
str"L000001"не
python
products = ["mortgage", "consumer", "credit_card", "consumer", "overdraft"]   # a list
record = ("L000001", "investment", 1044231.30)                                # a tuple
months = range(1, 13)                                                         # a range
loan_id = "L000001"                                                           # a string

for value in [products, record, months, loan_id]:
    print(type(value).__name__, len(value))
Променете кода и го изпълнете отново

Всеки елемент на поредицата има номер - своята позиция, или индекс. Индексът започва от 0.

Python брои от 0, SAS - от 1

Първият символ на string: SUBSTR(loan_id, 1, 1) в SAS и SQL, но loan_id[0] в Python. Първият елемент на SAS array е arr{1}; на Python list е items[0]. Това е най-честата грешка през първите седмици - броете внимателно.

Какво могат всички поредици ​

Всички поредици поддържат едни и същи общи операции - учите ги веднъж:

ОперацияПримерРезултат
дължинаlen(s)броят елементи
индексиранеs[0], s[-1]първият, последният елемент
slicings[1:3]част от поредицата
конкатенацияs + tдвете, съединени (не за range)
повторениеs * 3три пъти (не за range)
принадлежностx in s, x not in sTrue / False
минимум, максимумmin(s), max(s)най-малкият, най-големият елемент
броенеs.count(x)колко пъти x е в s
търсенеs.index(x)индексът на първия x

Всеки тип има и собствени методи: lists могат да се сортират и променят, strings могат да станат с главни букви. Reference: Common sequence operations.

2. Lists ​

Какво е list? ​

List е променяема (mutable) поредица от стойности:

  • можете да добавяте, премахвате и променяте елементи: list може да расте и да се свива;
  • елементите могат да са от всякакъв тип, и от различни типове в един list;
  • една и съща стойност може да е в list много пъти.

Lists са контейнерът за всичко в Python. List е близо до колона на таблица или до SAS array - но може да расте и живее в паметта.

Създаване на list ​

List се пише в квадратни скоби, с елементи, разделени със запетаи:

python
empty = []
stages = [1, 2, 3]
products = ["mortgage", "consumer", "credit_card"]
loan = ["L000001", "investment", 1044231.30, 8.83]     # different types
matrix = [                                             # a list of lists (session 5)
    [1, 2, 3],
    [4, 5, 6],
]
print(empty, stages, products)
print(loan)
print(len(products), len(empty), len(matrix))
Променете кода и го изпълнете отново

Четене на елемент ​

text
item = list_name[index]
python
fruits = ["apple", "banana", "strawberry", "banana", "orange"]
print(fruits[0])        # the first item: apple
print(fruits[2])        # the third item: strawberry
print(fruits[-1])       # the last item: orange
Променете кода и го изпълнете отново

Индекс, който не съществува, е грешка:

python
fruits = ["apple", "banana", "strawberry"]
print(fruits[3])        # 3 items: the indexes are 0, 1, 2
Променете кода и го изпълнете отново

Промяна на елемент ​

text
list_name[index] = value
python
fruits = ["apple", "banana", "strawberry"]
fruits[1] = "plum"          # change the second item
print(fruits)
fruits[-1] = "orange"       # change the last item
print(fruits)
Променете кода и го изпълнете отново

del премахва елемент по индекса му:

python
fruits = ["apple", "plum", "orange"]
del fruits[1]
print(fruits)
Променете кода и го изпълнете отново

Цикъл по list ​

List е естественото нещо за обхождане: for взема елементите му един по един, по ред. Всичко от занятие 2 работи и тук - акумулатори, break с флаг и continue.

Сума, брой и най-големият елемент в един цикъл - като SUM, COUNT с условие и MAX в SQL:

python
amounts = [1044231.30, 651927.30, 644.25, 14286.75, 3200.00]

total = 0.0
count_small = 0
largest = amounts[0]                 # start with the first item
for amount in amounts:
    total += amount
    if amount < 10000:
        count_small += 1
    if amount > largest:
        largest = amount

print(f"total {total:,.2f}, average {total / len(amounts):,.2f}")
print(count_small, "small loans; the largest is", largest)
print(sum(amounts), max(amounts))    # the built-in functions do it too (section 4)
Променете кода и го изпълнете отново

Намерете елемент: спрете при първото съвпадение с break и запомнете с флаг дали е намерен (занятие 2):

python
watch_list = ["L000012", "L000025", "L000059"]
loan_id = "L000025"

found = False
for item in watch_list:
    if item == loan_id:
        found = True
        break

if found:
    print(loan_id, "is on the watch list")
else:
    print(loan_id, "is not on the watch list")
Променете кода и го изпълнете отново

Пробвайте

Сменете loan_id на "L000099" и предскажете резултата, преди да изпълните кода. (За просто "има ли го?" loan_id in watch_list е по-кратко - раздел 4.)

Прескочете елементите, които не искате, с continue - тук липсващите стойности на колона, прочетена от файл, които са празни strings:

python
ratings = ["3", "", "7", "2", "", "10"]

known = 0
total = 0
for rating in ratings:
    if rating == "":                # a missing value: skip it
        continue
    known += 1
    total += int(rating)

print(known, "known ratings, the average is", total / known)
Променете кода и го изпълнете отново

Цикълът дава елементите, не позициите им. Ако ви трябва позицията, бройте я сами (занятие 5 показва enumerate, който го прави вместо вас):

python
amounts = [800.0, 0.0, 15900.0, 0.0]

position = 0
for amount in amounts:
    if amount == 0:
        print("a zero amount at index", position)
    position += 1
Променете кода и го изпълнете отново

Два lists, един цикъл в друг - всяка двойка, като CROSS JOIN в SQL:

python
products = ["mortgage", "consumer"]
currencies = ["EUR", "USD"]

for product in products:
    for currency in currencies:
        print(product, currency)
Променете кода и го изпълнете отново

3. Индекси и slices ​

Индекси от началото и от края ​

  • Първият елемент има индекс 0; всеки следващ е с 1 повече.
  • Последният елемент има индекс -1; броейки назад, всеки е с 1 по-малко.
  • Валидни индекси са само цели числа.

List от шест кредита с индексите от 0 до 5 над елементите и от -6 до -1 под тях

python
numbers = [1, 2, 3, 4, 5]
print(numbers[0], numbers[1], numbers[2], numbers[3], numbers[4])
print(numbers[-1], numbers[-2], numbers[-3], numbers[-4], numbers[-5])
Променете кода и го изпълнете отново

Отрицателните индекси са удобни за "последните": последният месец в list от месечни стойности е values[-1], каквато и да е дължината на list-а.

Slices: [start:stop:step] ​

Slice взема част от поредица:

text
sequence[start:stop:step]
  • start - индексът на първия взет елемент (включен); празно = от началото;
  • stop - индексът, където slice-ът спира (този елемент не е включен); празно = до края;
  • step - взема всеки n-ти елемент; празно = 1. Отрицателна стъпка върви назад.
text
a[start:stop]     items from start up to stop - 1
a[start:]         items from start to the end
a[:stop]          items from the beginning up to stop - 1
a[:]              all items - a copy of the whole sequence

Правилото "stop не е включен" е същото като в range (занятие 2). То има хубава страна: a[:3] са първите 3 елемента, a[3:] - останалите, а a[2:5] има 5 - 2 = 3 елемента.

Лесен начин да го видите: мислете за числата на slice като за разрези между елементите, а не като за елементите. loans[1:4] реже при 1 и при 4 и взема това, което е между тях:

Същият list с разрезите от 0 до 6 между елементите; loans[1:4] взема елементите между разрез 1 и разрез 4

python
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[2:5])      # [2, 3, 4]
print(numbers[1:9:2])    # every 2nd item: [1, 3, 5, 7]
print(numbers[:3])       # the first 3: [0, 1, 2]
print(numbers[-3:])      # the last 3: [7, 8, 9]
print(numbers[::-1])     # all, reversed
numbers_copy = numbers[:]
print(numbers_copy)
Променете кода и го изпълнете отново
За напреднали: отрицателна стъпка със start или stop

С отрицателна стъпка slice-ът върви назад, затова start трябва да е по-големият индекс. За работа с данни ви трябва [::-1] (всичко, наобратно).

python
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[:-4:-1])   # the last 3, reversed: [9, 8, 7]
print(numbers[7:2:-1])   # from index 7 down to index 3: [7, 6, 5, 4, 3]
print(numbers[2:7:-1])   # [] - you cannot go down from 2 to 7
Променете кода и го изпълнете отново

Slice никога не дава IndexError - извън границите просто взема каквото има:

python
numbers = [0, 1, 2]
print(numbers[1:100])
print(numbers[5:10])
Променете кода и го изпълнете отново

Slices на strings: SUBSTR от SAS / SQL ​

String е поредица от символи, така че същите индекси и slices работят - това е версията на Python на SUBSTR от SAS и SQL:

SAS / SQLPython
SUBSTR(loan_id, 1, 1)loan_id[0]
SUBSTR(loan_id, 2)loan_id[1:]
SUBSTR(report_date, 1, 4)report_date[:4]
SUBSTR(report_date, 6, 2)report_date[5:7]
python
greeting = "Hello, World!"
print(greeting[1:5])            # 'ello'

loan_id = "L000123"
print(loan_id[0], loan_id[1:], int(loan_id[1:]))

report_date = "2026-09-30"
year, month, day = report_date[:4], report_date[5:7], report_date[8:]
print(year, month, day)
Променете кода и го изпълнете отново

Strings не могат да се променят: loan_id[0] = "X" е грешка. Методите на strings (занятие 7) правят нови strings.

Същото в pandas

pandas (занятие 14) използва същите правила. iloc взима редове и колони по позиция - от 0, без stop; .str[...] взима един и същ slice от всеки текст в колоната:

python
import pandas as pd

loans = pd.read_csv("data/loans.csv")
print(loans.iloc[:3, :4])                     # the first 3 rows, the first 4 columns
print(loans["start_date"].str[:4].head(3))    # SUBSTR(start_date, 1, 4) for every row
Променете кода и го изпълнете отново

4. Още общи операции ​

Конкатенация + и повторение * ​

+ съединява две поредици от един и същ тип в нова; * повтаря поредица. Множителят трябва да е цяло число.

python
num_list = [1, 2, 3]
alpha_list = ["a", "b", "c"]
print(num_list + alpha_list)      # a new list

record = ("L000001", "investment")
print(record + (1044231.30,))     # a new tuple, one item longer

print(num_list * 3)
print(alpha_list * 3)
print([0.0] * 12)                 # 12 months, all 0.0 - a common start value
Променете кода и го изпълнете отново

Само strings, lists и tuples могат да се съединяват и повтарят. range не може (но можете първо да направите list от него: list(range(3)) * 2).

+ прави нов list. Съединяването на два lists един под друг е UNION ALL в SQL или SET a b; в DATA step на SAS - например просрочените кредити от два месеца:

python
late_august = ["L000002", "L000014", "L000025"]
late_september = ["L000014", "L000059"]
late_both = late_august + late_september    # UNION ALL: L000014 is there twice
print(late_both)
print(len(late_both))
Променете кода и го изпълнете отново

(Раздел 12 показва UNION без повторенията.)

Принадлежност: in, not in ​

x in sequence е True, когато x е един от елементите - като IN (...) в SQL и SAS:

python
fruits = ["apple", "banana", "strawberry"]
numbers = [1, 2, 3]
print("banana" in fruits)
print("banana" in numbers)
print("banana" not in fruits)
print("banana" not in numbers)

r = range(0, 10)
print(3 in r, 21 in r)

product = "consumer"
if product in ["mortgage", "consumer", "credit_card", "overdraft"]:
    print("retail")
Променете кода и го изпълнете отново

При strings in търси част от string-а: "USD" in "EUR,USD" е True.

len, min, max, sum, count, index ​

python
outstanding = [1044231.30, 651927.30, 644.25, 5875.11]
print(len(outstanding))
print(min(outstanding), max(outstanding))
print(sum(outstanding))
print(sum(outstanding) / len(outstanding))      # the average

stages = [1, 2, 1, 1, 3, 2, 1]
print(stages.count(1))          # how many times 1 is in the list
print(stages.index(3))          # the index of the first 3
Променете кода и го изпълнете отново

min и max работят и върху strings (по азбучен ред, занятие 2). sum - само върху числа. index дава ValueError, когато стойността не е в list-а - първо проверете с in.

5. Създаване на lists от други стойности ​

list(): list от всяка поредица ​

list() прави нов list от елементите на всяка поредица (на всеки iterable):

python
l = list()                       # the same as l = []
print(l)

point3d = (4, 0, 3)
print(list(point3d))             # from a tuple

digits = range(0, 10)
print(list(digits))              # from a range

user_name = "ada byron"
print(list(user_name))           # from a string: one item per character
Променете кода и го изпълнете отново

От текст: split ​

split() (занятие 1) нарязва string на list от strings:

python
row = "L000002,C0132,investment,EUR,2019-04-16,2029-04-16,1977400.00,651927.30,5.44,35,2"
fields = row.split(",")
print(len(fields), fields[2], fields[-2])

text = "some words delimited by spaces"
print(text.split())              # without an argument: by spaces
Променете кода и го изпълнете отново

Събиране на стойности в цикъл: append ​

Започнете с празен list и добавяйте (append) стойност във всяка итерация. Това е версията на Python на statement-а OUTPUT от SAS: в SAS OUTPUT записва ред от DATA step-а в изходния набор от данни; в Python append добавя елемент в list-а.

python
late_loans = []

with open("data/loans.csv", encoding="utf-8") as file:
    file.readline()
    for line in file:
        fields = line.strip().split(",")
        if int(fields[9]) > 90:              # days_past_due
            late_loans.append(fields[0])     # loan_id

print(len(late_loans), "loans more than 90 days past due")
print(late_loans[:5], "...")
Променете кода и го изпълнете отново
sas
data late_loans;
    set loans;
    if days_past_due > 90 then output;
    keep loan_id;
run;

6. Методите на list ​

Добавяне, премахване, обръщане, сортиране ​

МетодКакво прави
lst.append(x)добавя x в края
lst.extend(other)добавя всички елементи на other в края
lst.insert(i, x)вмъква x преди индекса i
lst.pop()премахва последния елемент и го връща
lst.pop(i)премахва елемента на индекс i и го връща
lst.remove(x)премахва първия елемент, равен на x (ValueError, ако няма такъв)
lst.clear()премахва всички елементи
lst.index(x), lst.count(x)индексът на първия x; колко x има
lst.reverse()обръща list-а на място
lst.sort()сортира list-а на място; sort(reverse=True): от най-големия
lst.copy()нов list със същите елементи (раздел 7)
del lst[i], del lst[i:j]премахва елемент, slice
python
fruits = ["apple", "banana", "strawberry"]

fruits.append("plum")               # at the end
print(fruits)

fruits.insert(2, "NEW")             # before the index 2
print(fruits)

item = fruits.pop()                 # remove the last item, and get it
print(item, fruits)

item = fruits.pop(2)                # remove the item at index 2
print(item, fruits)

fruits.remove("banana")             # remove by value
print(fruits)

fruits.extend(["kiwi", "lemon"])    # add several items
print(fruits)

fruits.reverse()
print(fruits)

numbers = [1, 3, 4, 2]
numbers.sort()
print(numbers)
numbers.sort(reverse=True)
print(numbers)
Променете кода и го изпълнете отново

Още: More on lists в tutorial-а.

append или extend? ​

python
a = [1, 2]
a.append([3, 4])        # one item: the list [3, 4]
print(a, len(a))

b = [1, 2]
b.extend([3, 4])        # two items: 3 and 4
print(b, len(b))
Променете кода и го изпълнете отново
За напреднали: не махайте елементи, докато обхождате същия list

Цикълът минава през list-а по позиция. Когато махнете елемент, следващият се премества на неговото място - и цикълът го прескача:

python
amounts = [0.0, 0.0, 500.0, 0.0]
for amount in amounts:
    if amount == 0:
        amounts.remove(amount)
print(amounts)             # [500.0, 0.0] - one zero is still there
Променете кода и го изпълнете отново

Вместо това изградете нов list - същото като да запишете нов data set в SAS:

python
amounts = [0.0, 0.0, 500.0, 0.0]
non_zero = []
for amount in amounts:
    if amount != 0:
        non_zero.append(amount)
print(non_zero)
Променете кода и го изпълнете отново

(Занятие 5 прави същото на един ред, с list comprehension.)

Методите променят list-а - и връщат None ​

append, sort, reverse и другите променят list-а на място. Те връщат None, а не list-а. Класически бъг:

python
numbers = [3, 1, 2]
numbers = numbers.sort()       # wrong: sort() returns None
print(numbers)
Променете кода и го изпълнете отново
Отговор

numbers.sort() сортира list-а и връща None, а numbers = ... свързва името с този None - сортираният list е загубен. Пишете само numbers.sort() или използвайте sorted() (следва).

sort() или sorted()? ​

  • lst.sort() сортира този list. Има го само при lists.
  • sorted(x) връща нов, сортиран list и не променя x. Работи върху всяка поредица: tuple, string, set, range.
python
names = ["Maria", "Ivan", "Asen"]
sorted_names = sorted(names)
print(names)               # not changed
print(sorted_names)
print(sorted("python"))    # the characters of a string, sorted
print(sorted(names, reverse=True))
Променете кода и го изпълнете отново

Сортирането сравнява елементите с < (занятие 2): strings - по азбучен ред, главните букви преди малките, а смесица от числа и strings е грешка:

python
print(sorted(["banana", "Cherry", "apple"]))
print(sorted([3, "1", 2]))
Променете кода и го изпълнете отново

PROC SORT в SAS и ORDER BY в SQL могат да сортират по колона или по няколко. За записи, пазени като tuples, сложете колоната за сортиране първа (раздел 8, "Сортиране на записи"); общият начин е sorted(..., key=...): занятия 4 и 5.

Същото в pandas

Методите на pandas работят като sorted(): връщат нова таблица и не променят старата (занятие 14). Запазете резултата в име:

python
import pandas as pd

loans = pd.read_csv("data/loans.csv")
by_size = loans.sort_values("outstanding", ascending=False)   # ORDER BY outstanding DESC
print(loans["loan_id"].head(3).tolist())      # not changed
print(by_size["loan_id"].head(3).tolist())    # a new, sorted table
Променете кода и го изпълнете отново

7. List е mutable: имена и референции ​

Две имена, един list ​

Занятие 1 показа, че променливата е име, свързано с обект. При числа и strings това почти няма значение, защото те не могат да се променят. List може да се променя - и тогава това има голямо значение:

python
original = [1, 2, 3]
new_list = original          # not a copy: the same list has a second name

new_list[0] = 100
print(original)              # [100, 2, 3] - changed too!
print(new_list)
print(original is new_list, id(original) == id(new_list))
Променете кода и го изпълнете отново

new_list = original не копира нищо. Двете имена сочат към един и същ обект list в паметта, така че промяна през едното име се вижда през другото. is (и id()) го показва.

Направете копие ​

Когато ви трябва независим list, направете копие. Три начина дават едно и също:

python
original = [1, 2, 3]

copy1 = original[:]          # a slice of everything
copy2 = original.copy()      # the copy() method
copy3 = list(original)       # list() makes a new list

copy1.append(4)
print(original, copy1, copy2, copy3)
print(original is copy1)
Променете кода и го изпълнете отново

Това са плитки (shallow) копия: новият list има същите елементи. Когато елементите сами са lists (таблица като list от редове), редовете пак са общи - занятие 5 обяснява плитките и дълбоките копия.

Пробвайте

В първия пример на този раздел напишете new_list = original.copy() вместо new_list = original. Предскажете какво показват сега print(original) и original is new_list, после изпълнете кода.

8. Tuples ​

Какво е tuple? ​

Tuple е непроменима (immutable) поредица: щом е създаден, не може да се промени - не може да се добави, премахне или замени елемент.

  • Подходящ е за фиксирани данни: запис (един ред от таблица), дата, двойка координати.
  • Tuple използва малко по-малко памет от list и е малко по-бърз.
  • Tuples могат всичко, което може една поредица (раздел 1), но имат само два метода: count и index.

Създаване на tuple ​

Tuple се пише в кръгли скоби, с елементи, разделени със запетаи:

python
t = ()
print(type(t))              # an empty tuple

t = (99,)                   # one item: note the comma!
print(type(t), t)

t = (99)                    # without the comma, it is just 99 in brackets
print(type(t), t)

point3d = (4, 0, 3)
print(point3d)

point3d = 4, 0, 3           # the brackets may be left out
print(point3d)
Променете кода и го изпълнете отново

Запетаята прави tuple, а не скобите. Затова x, y = 1, 2 (занятие 1) работи: дясната страна е tuple.

Четене на елементите ​

python
address = ("Bulgaria", "Sofia", "Nezabravka str", 14)
country = address[0]
town = address[1]
print(country, town, address[-1])

ada_birth_date = (10, "December", 1815)
print(f"Ada was born on {ada_birth_date[1]} {ada_birth_date[0]}, {ada_birth_date[2]}")
Променете кода и го изпълнете отново

Tuple не може да се променя ​

python
address = ("Bulgaria", "Sofia", "Nezabravka str", 14)
address[0] = "France"
Променете кода и го изпълнете отново

За да "промените" tuple, правите нов (името се премества към него, както при числата):

python
record = ("L000001", "investment", 1)
record = record[:2] + (2,)          # a new tuple with stage 2
print(record)
Променете кода и го изпълнете отново

Анимация

Lists и референции: 5. Tuple не може да се променя

За напреднали: list вътре в tuple може да се променя

Tuple не може да промени кои обекти държи. Ако един от тях е list, самият list все пак може да се променя:

python
t = ([1, 2, 3], "fixed")
t[0][2] = 100              # change the list inside the tuple
print(t)
Променете кода и го изпълнете отново
python
users = (
    ["Ivan", "Ivanov", 34],
    ["Maria", "Ivanova", 36],
)
users[0][2] = 100               # the list inside: works
print(users[0])
users[0] = ["Petar", "Petrov", 45]   # the tuple itself: TypeError
Променете кода и го изпълнете отново

Същото важи за tuple от lists - фиксирана структура от групи, които могат да се променят отвътре:

python
products = (
    ["Shirt", "Pants"],      # the names
    [20, 35],                # the quantities
)
products[1][0] = 100         # a quantity can change
print(products)
Променете кода и го изпълнете отново

Анимация: 6. List вътре в tuple.

Разопаковане (unpacking) ​

Tuple (или list) може да се разопакова в няколко имена наведнъж - броят на имената трябва да е равен на броя на елементите:

python
record = ("L000002", "investment", 651927.30)

loan_id, product, outstanding = record
print(loan_id)
print(product)
print(outstanding)
Променете кода и го изпълнете отново
python
loan_id, product = ("L000002", "investment", 651927.30)     # 2 names, 3 items
Променете кода и го изпълнете отново

Когато ви трябват само някои от стойностите, пак ви трябва име за всяка от останалите - броят трябва да съвпада. _ е обичайното име за стойност, която не ви трябва. То е нормално име, но казва на читателя "пренебрегнато":

python
row = ("L000002", "C0132", "investment", "EUR", 651927.30)

loan_id, _, product, _, outstanding = row       # skip client_id and currency
print(loan_id, product, outstanding)
Променете кода и го изпълнете отново

Пробвайте

Махнете едно _ от разопаковането и предскажете грешката, преди да изпълните кода.

За напреднали: звездичката взима "останалото"

Име със звездичка * взема "останалото", като list:

python
row = ("L000002", "C0132", "investment", "EUR", 651927.30)
loan_id, client_id, *other = row
print(loan_id, client_id, other)

first, *middle, last = [1, 2, 3, 4, 5]
print(first, middle, last)

loan_id, *_ = row                               # only the first value
print(loan_id)
Променете кода и го изпълнете отново

Tuples като записи ​

List от tuples е малка таблица: всеки tuple е ред, всяка позиция - колона. for с разопаковане (занятие 2) я чете ред по ред:

python
loans = [
    ("L000001", "investment", 1044231.30, 0),
    ("L000002", "investment", 651927.30, 35),
    ("L000003", "credit_card", 644.25, 0),
    ("L000014", "consumer", 14286.75, 200),
]

print(loans[1])             # the second row
print(loans[1][2])          # its third column
print(loans[-1][0])         # the first column of the last row

for loan_id, product, outstanding, days_past_due in loans:
    if days_past_due > 30:
        print(loan_id, product, outstanding)
Променете кода и го изпълнете отново

Често използване на list от tuples - етикет със стойността си - и разделянето му отново на два lists:

python
exposures = [("L000001", 1044231.30), ("L000002", 651927.30), ("L000003", 644.25)]

loan_ids = []
amounts = []
for loan_id, amount in exposures:
    loan_ids.append(loan_id)
    amounts.append(amount)
print(loan_ids)
print(amounts)
Променете кода и го изпълнете отново

Tuples се използват и за няколко върнати стойности на функция (занятие 4) и като ключове на dictionary (занятие 6), където list не е позволен.

Сортиране на записи: tuples се сравняват елемент по елемент ​

Tuples се сравняват като думи, елемент по елемент: първо първите елементи; само когато са равни - вторите, и т.н. Затова sorted върху list от tuples сортира по първия елемент, после по втория - като ORDER BY col1, col2 в SQL:

python
print((2, "b") < (10, "a"))         # True: 2 < 10, the rest is not looked at
print((5, "b") < (5, "c"))          # True: 5 == 5, so "b" < "c" decides

pairs = [(5, "L000003"), (12, "L000001"), (5, "L000002")]
print(sorted(pairs))                # by the number, then by the id
print(sorted(pairs, reverse=True))  # from the largest
Променете кода и го изпълнете отново

За да сортирате записи по друга колона, изградете нови tuples с тази колона първа - най-големите експозиции:

python
loans = [
    ("L000001", "investment", 1044231.30),
    ("L000002", "investment", 651927.30),
    ("L000003", "credit_card", 644.25),
    ("L000014", "consumer", 14286.75),
]

by_size = []
for loan_id, product, outstanding in loans:
    by_size.append((outstanding, loan_id))      # the amount first

for outstanding, loan_id in sorted(by_size, reverse=True)[:2]:
    print(loan_id, f"{outstanding:,.2f}")
Променете кода и го изпълнете отново

Два чифта скоби

by_size.append((outstanding, loan_id)): външните скоби са на извикването, вътрешните правят tuple-а. append(outstanding, loan_id) дава две стойности на append - TypeError.

За напреднали: named tuples

В обикновен tuple loan[2] не казва коя е третата колона. Named tuple дава имена на позициите - лек запис:

python
from collections import namedtuple

Loan = namedtuple("Loan", ["loan_id", "product", "outstanding"])
loan = Loan("L000001", "investment", 1044231.30)

print(loan)
print(loan.outstanding)          # by name
print(loan[2])                   # still by position
loan_id, product, outstanding = loan
Променете кода и го изпълнете отново

Класовете и data classes (занятия 11 - 12) отиват по-далеч: записи, които могат и да проверяват данните си и имат собствени методи.

9. Lists или tuples? ​

ListTuple
Скоби[1, 2, 3](1, 2, 3)
Може да се променяда: добавяне, премахване, сортиранене
Използвайте заколекция, която расте: кредитите на един отчет, стойностите на една колонафиксиран запис: един ред, дата, двойка
Памет, скоростмалко повече, малко по-бавномалко по-малко, малко по-бързо
За напреднали: вижте паметта
python
import sys

as_list = [1044231.30, 651927.30, 644.25]
as_tuple = (1044231.30, 651927.30, 644.25)
print(sys.getsizeof(as_list), "bytes for the list")
print(sys.getsizeof(as_tuple), "bytes for the tuple")
Променете кода и го изпълнете отново

(Точните числа зависят от версията на Python; tuple винаги е по-малък.)

Практическо правило: list за много стойности от един и същ вид (всички номера на кредити), tuple за няколко стойности от различен вид, които са свързани (един кредит).

Преобразуване между тях ​

list() и tuple() преобразуват в двете посоки:

python
address = ("Bulgaria", "Sofia", "Nezabravka str", 14)
address = list(address)          # now it can change
address[3] = 25
print(address)

fruits = ["apple", "banana", "strawberry", "banana", "orange"]
print(tuple(fruits))
print(tuple(range(0, 10)))
print(tuple("ada byron"))
Променете кода и го изпълнете отново

10. range ​

Синтаксис ​

range дава непроменима поредица от цели числа:

text
range(stop)
range(start, stop)
range(start, stop, step)
  • stop е единственият задължителен аргумент. Числата свършват на stop - 1.
  • start е 0, когато е пропуснат; step е 1, когато е пропуснат.
  • И трите трябва да са цели числа. step може да е отрицателен, но не и 0 (ValueError).
  • Ако аргументите не дават числа, range-ът е празен.

Reference: Ranges.

python
print(list(range(0, 10)))        # 0 ... 9
print(list(range(10)))           # the same
print(list(range(2, 10, 2)))     # 2, 4, 6, 8
print(list(range(9, -1, -1)))    # 9 ... 0, backwards
print(list(range(-3, 4)))        # -3 ... 3
print(list(range(9, -1, 1)))     # empty: you cannot go from 9 up to -1
Променете кода и го изпълнете отново

Циклите DO на SAS и техните range са в таблицата на занятие 2, раздел 10. Ново днес: range назад - do i = 10 to 1 by -1; е range(10, 0, -1).

Range не пази числата си ​

Range пази само start, stop и step и изчислява всяко число, когато потрябва. Когато отпечатате range, виждате само "формулата". За да видите числата, направете list или tuple от него - или го обходете с цикъл:

python
r = range(0, 10, 2)
print(r)
print(list(r))
for i in r:
    print(i, end=" ")
Променете кода и го изпълнете отново
За напреднали: range е малък и е истинска поредица

Range от милиард числа е толкова малък в паметта, колкото range от десет - list пази всяко число:

python
import sys

print(sys.getsizeof(range(10)), sys.getsizeof(range(1_000_000_000)))
print(sys.getsizeof(list(range(1000))))     # a list keeps every number
Променете кода и го изпълнете отново

len, индекси, slices и in работят върху range, и то бързо:

python
r = range(0, 1_000_000, 5)
print(len(r), r[3], r[-1], 500 in r, 501 in r)
print(range(10)[:3])
Променете кода и го изпълнете отново

11. Sets ​

Какво е set? ​

Set е неподредена колекция от уникални стойности - множествата от математиката.

  • Без повторения: всяка стойност е в set веднъж.
  • Без ред: set няма първи и последен елемент и няма индекси.
  • Елементите трябва да са непроменими (hashable): числа, strings, tuples - не lists.
  • Самият set е променяем: можете да добавяте и премахвате елементи.

Sets се използват за проверка на принадлежност, за премахване на повторения и за сравняване на групи: обединение, сечение, разлика (раздел 12).

Създаване на set ​

Set се пише в къдрави скоби:

python
set_of_immutables = {"a", 1, "b", 2, 3}
print(set_of_immutables)

int_dup_numbers = {1, 2, 3, 1, 2, 4, 3, 5, 1, 2, 3}
print(int_dup_numbers)                       # the duplicates are gone
print(int_dup_numbers == {1, 2, 3, 4, 5})    # order and duplicates do not matter
Променете кода и го изпълнете отново
python
set_of_mutables = {[1, 2, 3], ["a", "b"]}    # TypeError: a list is unhashable
Променете кода и го изпълнете отново

{} не е празен set

{} е празен dictionary (занятие 6): колекция от двойки key: value, докато set има само стойности. Празният set е set():

python
print(type({}), type(set()))
Променете кода и го изпълнете отново

Уникални стойности: SELECT DISTINCT от SQL ​

В SQL SELECT DISTINCT дава всяка стойност на колона по веднъж. В SAS пишете същото в PROC SQL или използвате PROC SORT с NODUPKEY:

sql
SELECT DISTINCT product FROM loans;
sas
proc sort data=loans(keep=product) out=products nodupkey;
    by product;
run;

В Python set() прави set от всяка поредица - повторенията изчезват:

python
l1 = [1, 2, 1, 3, 2, 5, 3]
l2 = list(set(l1))
print(l2)

products = ["consumer", "mortgage", "consumer", "overdraft", "mortgage"]
print(set(products))                 # SELECT DISTINCT product
print(len(set(products)))            # COUNT(DISTINCT product)
print(sorted(set(products)))         # SELECT DISTINCT ... ORDER BY
Променете кода и го изпълнете отново

Върху данните - различните продукти в loans.csv:

python
products = set()
with open("data/loans.csv", encoding="utf-8") as file:
    file.readline()
    for line in file:
        products.add(line.split(",")[2])
print(len(products), sorted(products))
Променете кода и го изпълнете отново

Редът на set не се запазва

Set не помни реда, в който са дошли стойностите. Отпечатайте един и същ set от strings при две пускания на програмата и редът може да е различен. Когато редът има значение - в отчет, в тест - използвайте sorted(the_set). (За да премахнете повторенията и да запазите първоначалния ред: list(dict.fromkeys(items)), занятие 6.)

Добавяне и премахване ​

python
my_set = {1, 2, 3}
my_set.add(4)
print(my_set)
my_set.add(2)               # already there: nothing changes
print(my_set)
my_set.discard(3)           # remove a value (no error if it is not there)
print(my_set)
Променете кода и го изпълнете отново

len, in, min, max, sum и for работят както при lists.

За напреднали: remove, pop, clear

remove(x) също маха x, но спира с KeyError, когато x не е в set-а. pop() маха и връща някакъв елемент (кой - не е определено), clear() изпразва set-а.

python
my_set = {1, 2, 3}
my_set.remove(2)
print(my_set)
my_set.remove(5)            # KeyError: 5 is not there
Променете кода и го изпълнете отново

Sets са бързи за in ​

Отвътре set е hash таблица: Python изчислява от стойността къде трябва да е тя и търси само там. List се претърсва елемент по елемент, от началото. За in върху голяма колекция set е много по-бърз; за обхождане на всички елементи и за достъп по индекс list е по-добър.

Затова, когато проверявате много стойности срещу дълъг списък от позволени кодове - номенклатура - първо сложете кодовете в set.

За напреднали: измерете го
python
import time

large_list = list(range(1_000_000))
large_set = set(large_list)

start = time.perf_counter()
found = 999_999 in large_list
list_time = time.perf_counter() - start

start = time.perf_counter()
found = 999_999 in large_set
set_time = time.perf_counter() - start

print(f"in a list: {list_time:.6f} s")
print(f"in a set:  {set_time:.6f} s")
Променете кода и го изпълнете отново
За напреднали: frozenset

frozenset е непроменим set. Понеже не може да се променя, може да бъде елемент на set или ключ на dictionary:

python
pairs = {frozenset({"EUR", "USD"}), frozenset({"EUR", "BGN"})}
print(frozenset({"USD", "EUR"}) in pairs)      # the order does not matter
Променете кода и го изпълнете отново

12. Операции със sets: UNION, INTERSECT, EXCEPT от SQL ​

Четирите операции ​

Четири диаграми на Вен за два sets a и b: обединение, сечение, разлика и симетрична разлика, с оператора на Python, метода и ключовата дума на SQL за всяка

ОперацияОператорМетодРезултатSQL (и в PROC SQL на SAS)
обединениеa | ba.union(b)в a или в bUNION
сечениеa & ba.intersection(b)и в дватаINTERSECT
разликаa - ba.difference(b)в a, не в bEXCEPT (в по-старите Oracle: MINUS)
симетрична разликаa ^ ba.symmetric_difference(b)в единия от тях, но не и в двата-

Всяка операция връща нов set.

python
set1 = {1, 2, 3, 4}
set2 = {5, 4}

print(set1 | set2, set1.union(set2))
print(set1 & set2, set1.intersection(set2))
print(set1 - set2, set1.difference(set2))
print(set1 ^ set2, set1.symmetric_difference(set2))
Променете кода и го изпълнете отново

UNION или UNION ALL?

UNION в SQL премахва повторенията - това е обединение на sets, a | b. UNION ALL пази всеки ред - това е съединяване на два lists, list_a + list_b (раздел 4).

За напреднали: методите приемат всяка поредица

Методите приемат всяка поредица, операторите - само sets. a.isdisjoint(b) е True, когато двата set-а нямат общи елементи.

python
regions = {"Sofia", "Varna"}
print(regions.union(["Ruse", "Sofia"]))
print(regions.isdisjoint({"Ruse", "Pleven"}))
print(regions | ["Ruse", "Sofia"])          # TypeError
Променете кода и го изпълнете отново

Пример: клиентите от два месеца ​

Номерата на клиентите с кредит в края на август и в края на септември:

python
august = {"C0001", "C0002", "C0003", "C0005", "C0008"}
september = {"C0002", "C0003", "C0005", "C0013", "C0021"}

print("new:     ", sorted(september - august))      # EXCEPT
print("closed:  ", sorted(august - september))
print("stayed:  ", sorted(august & september))      # INTERSECT
print("all:     ", len(august | september))         # UNION
print("changed: ", sorted(august ^ september))      # new or closed
Променете кода и го изпълнете отново

Пробвайте

Добавете "C0001" към september и предскажете петте реда, преди да изпълните блока.

Пример: какво липсва в кредитно досие ​

python
required = {"id_card", "income_statement", "collateral_valuation", "insurance"}
submitted = {"id_card", "insurance", "bank_statement"}

print("Received:", ", ".join(sorted(required & submitted)))
print("Missing: ", ", ".join(sorted(required - submitted)))
print("Extra:   ", ", ".join(sorted(submitted - required)))
Променете кода и го изпълнете отново

", ".join(...) прави един string от елементите, с ", " между тях (занятие 7).

Подмножества и проверки на данните ​

ПроверкаОператорМетод
всеки елемент на a е в ba <= ba.issubset(b)
b има всеки елемент на ab >= ab.issuperset(a)

Това са бързи проверки на данните, като проверките, които пишете в SQL преди join:

python
allowed_currencies = {"EUR", "USD"}

currencies = set()
loan_clients = set()
with open("data/loans.csv", encoding="utf-8") as file:
    file.readline()
    for line in file:
        fields = line.split(",")
        loan_clients.add(fields[1])
        currencies.add(fields[3])

clients = set()
with open("data/clients.csv", encoding="utf-8") as file:
    file.readline()
    for line in file:
        clients.add(line.split(",")[0])

print("only allowed currencies:", currencies <= allowed_currencies)
print("every loan has a known client:", loan_clients <= clients)
print("loans of unknown clients:", loan_clients - clients)       # LEFT JOIN ... WHERE c.client_id IS NULL
Променете кода и го изпълнете отново

Същото в pandas

Същата проверка на две таблици преди JOIN - със set() от колона или с isin (занятие 15; там merge(..., indicator=True) показва редовете без съвпадение):

python
import pandas as pd

loans = pd.read_csv("data/loans.csv")
clients = pd.read_csv("data/clients.csv")
print(set(loans["client_id"]) - set(clients["client_id"]))    # EXCEPT
print(loans["client_id"].isin(clients["client_id"]).all())    # every loan has a known client
Променете кода и го изпълнете отново

13. Упражнения ​

Отворете своето копие на Homework repository във VS Code (преди занятието: вземете новите задачи) и пускайте задачите от терминала, в главната папка на repository-то:

bash
python 03-lists-tuples-sets/practice/task1-predict-sequences.py
ЗадачаФайлКакво правите
1task1-predict-sequences.pyПредвидете 14 индекса, slices и операции, после проверете
2task2-slices.pyЧасти от номера и дати; тримесечия от година месечни стойности
3task3-list-methods.pyСписък за наблюдение: добавяне, вмъкване, премахване, сортиране, броене
4task4-tuples.pyЗаписи като tuples: разопаковане, четене, сумиране; list от tuples
5task5-sets.pyКлиентите от два месеца: нови, закрити, останали; кредитно досие
6task6-from-file.pyОт loans.csv и clients.csv: различни стойности, lists, проверки със sets

Помощ за задачите и домашното

  • Какво иска всяка задача. Задачи 1 - 2: раздели 1 - 5 (в задача 1 редовете с (99,), sort / sorted и sets искат раздели 6, 8, 11 и 12). Задача 3: раздели 6 - 7. Задача 4: раздел 8. Задачи 5 - 6 и домашното: раздели 8 - 12. Ако раздел още не е минат в час, започнете със задачите преди него или първо прочетете раздела. Част, която не сте започнали, показва TODO, а не FAIL.
  • Задача 1: type(x).__name__ е името на типа, като текст: "tuple", "list".
  • Задача 3: първо направете backup с watch.copy() - backup = watch само би дало второ име на същия list (раздел 7). extend добавя няколко елемента с едно извикване; pop() маха последния елемент и го връща; sort(reverse=True) сортира от най-големия. Методите променят watch и връщат None: пишете watch.sort(), а не watch = watch.sort() (раздел 6).
  • Задача 4, част 2: record[:-1] е записът без последния му елемент (нов tuple), (40,) е tuple с един елемент (раздел 8), а + ги съединява.
  • Задача 4, част 4: четирите резултата - в един цикъл; преди него total = 0.0 и три празни lists.
  • Задача 6, част 1: both_currencies - в цикъла съберете клиентите на кредитите в EUR в един set, а на тези в USD - в друг; след цикъла пресечете двата set-а с &.
  • Четене на файл (задача 6, домашното): както в занятие 2 - file.readline() прескача заглавния ред, после fields = line.strip().split(",") в цикъла. Колоните са в реда на заглавния ред (задача 6 ги изброява; страницата примерни данни също): fields[0] е първата.
  • Домашно, част 1: преобразувайте, докато добавяте: loans.append((fields[0], ..., float(fields[7]), int(fields[10]))) - обърнете внимание на двата чифта скоби (раздел 8).
  • Домашно, част 2: при сортиране tuples се сравняват първо по първите си елементи - раздел 8, "Сортиране на записи: tuples се сравняват елемент по елемент".
  • Домашно, част 5: два цикъла, по един за всеки файл: първо съберете клиентите в stage 3 в set, после прочетете clients.csv и запазвайте региона, когато клиентът е в този set.

Обобщение ​

ТемаЗапомнете
Поредициlist, tuple, range, str: подредени; len, in, +, *, min, max, count, index
Индексиот 0; -1 е последният; грешен индекс е IndexError
Slicess[start:stop:step], stop не е включен; s[:3] първите 3, s[-3:] последните 3, s[::-1] обърнат, s[:] копие
Lists[...], mutable; append, extend, insert, pop, remove, sort; методите връщат None
Сортиранеlst.sort() променя list-а; sorted(x) връща нов list
Референцииb = a е същият list с две имена; копие: a[:], a.copy(), list(a)
Tuples(...), immutable, прави го запетаята: (99,); разопаковане a, b, c = t, _ за стойност, която не ви трябва; записи
rangerange(start, stop, step), stop не е включен; малък в паметта
Sets{...} / set(), уникални, неподредени; add, discard; бърз in; отпечатване със sorted()
Операции със sets| е UNION от SQL, & е INTERSECT, - е EXCEPT; ^ симетрична разлика; <= подмножество

Домашно ​

  1. 03-lists-tuples-sets/homework.py (в homework repository): моментна снимка на портфейла от loans.csv - кредитите като list от tuples, петте най-големи експозиции, различните продукти, клиентите с ипотечен и потребителски кредит едновременно и регионите на клиентите в stage 3. Всяка проверка трябва да отпечата OK.
  2. Задачите за упражнение, които не сте довършили в клас.
  3. По желание: names.py пита колко имена ще въведе потребителят, чете ги с input() в цикъл в list и ги отпечатва сортирани, без повторения, и колко са били повторенията.
  4. По желание: една дума е палиндром, ако се чете еднакво отпред назад и отзад напред, без значение главни или малки букви ("Racecar"). Проверете го със slice, за думи, въведени от потребителя.
  5. По желание: 03-lists-tuples-sets/notebook.ipynb - кратки допълнителни задачи по днешните теми в Jupyter notebook, всяка с клетка за проверка. Отворете го във VS Code и изберете kernel-а .venv.

Още за четене ​

Следващото занятие: функции - собствен def, параметри и върнати стойности и защо функцията е версията на SAS macro в Python.

Създаден за