Skip to content

Latest commit

 

History

History
519 lines (491 loc) · 41.6 KB

File metadata and controls

519 lines (491 loc) · 41.6 KB

Коллекции

  1. Проблема
  2. Что такое коллекция
  3. List
  4. Tuple
  5. Set
  6. Dictionary
  7. Общее для коллекций
  8. Модель данных в Python
  9. Homework
  10. Cursed questions
  • Представим, мы пишем программу, которая позволяет выполнять грузоперевозки по морю. Можем представить ее в следующем виде:
    cargo_TV = "LG v 7"
    cargo_phone = "Samsung 10s"
    cargo_microwave = "Samsung ME83X"
    Однако, если нужно добавить еще одно именование, нужно будет заводить новые переменные с новыми значениями, кроме того нужно будет писать для этих переменных какой-то код для обработки этих грузов, из-за этого программный код будет разрастаться, а программа перестанет быть универсальной. В этих примерах данные однородны, т.е. для их обработки скорее всего будет использоваться один и тот же способ доставки и оформления.
  • Для решения проблем работы с однородными данными, придумали коллекции. Это структура, которая позволяет работать с множеством элементов, как с одним целым.
  • Коллекции в Python - это не просто типы, а структуры данных. Есть множество структур данных, и все они служат для одной цели: представлять множество элементов как одно целое.
  • В Python есть специальный тип, называемый list(ru: список). Можно себе представить list как одну сущность, в которую складываются другие сущности, например как контейнер для грузоперевозок:
    cargo_TV = "LG v 7"
    cargo_phone = "Samsung 10s"
    cargo_microwave = "Samsung ME83X"
    # упаковываем в лист, чтобы дальше было проще работать
    cargo_container = [cargo_TV, cargo_phone, cargo_microwave]
    Теперь мы можем работать со всеми элементами, как с одним целым. Например переправить сразу все элементы, или удалить их.
  • Можно группировать старые элементы в список, как мы сделали с уже существующими элементами cargo в предыдущем примере, а можно объявлять значения сразу в списке:
    cargo_container = ["LG v 7", "Samsung 10s", "Samsung ME83X"]
  • У списков есть специальный синтаксис для обращения к элементам. У каждого элемента есть специальный номер, при помощи которого к нему можно обратиться. Например в списке cargo_container 3 элемента: "LG v 7", "Samsung 10s", "Samsung ME83X", однако нумерация в списках идет с 0-го элемента, то есть элемент "LG v 7" будет иметь индекс(номер) 0. Обращаться к элементу можно через квадратные скобки - [], например, чтобы получить "LG v 7" нужно написать:
    cargo_container = ["LG v 7", "Samsung 10s", "Samsung ME83X"]
    cargo_TV = cargo_container[0]
    print(cargo_TV) # выведет "LG v 7"
    Чтобы получить "Samsung 10s" - 2-й элемент списка, надо обратиться к нему по индексу 1:
    cargo_container = ["LG v 7", "Samsung 10s", "Samsung ME83X"]
    cargo_phone  = cargo_container[1]
    print(cargo_phone) # выведет "Samsung 10s"
  • Хранить в списке можно не только строки, но и другие типы данных:
    cargo_weights = [15, 0.2, 10]
    print(cargo_weights[0]) # выведет 15
    Можно хранить даже данные разных типов, однако лучше так не делать, так как одинаково обрабатывать данные в последующем будет сложно:
    cargo_weights = [15, "0.2", 10]
    print(cargo_weights[1]) # выведет 0.2
  • Хранить в списке можно даже другие списки, например:
    cargo_weights = [15, "0.2", 10]
    cargo_container = ["LG v 7", "Samsung 10s", "Samsung ME83X"]
    cargo_info = [cargo_container, cargo_weights]
    print(cargo_info[0]) # Вывод: ['LG v 7', 'Samsung 10s', 'Samsung ME83X']
  • Итак, инициализацию(объявление) списка можно делать как с готовыми переменными, так и просто со значениями. Как с данными одного типа, так и с данными разных типов. Можно получать данные из списка. А можно ли его изменять?
  • Изменять элемент в списке можно, если обратиться к нему по индексу списка и присвоить туда новое значение:
    cargo_weights = [15, "0.2", 10]
    cargo_weights[0] = 20
    print(cargo_weights) #Вывод:  [20, "0.2", 10]
    В примере мы изменили первый элемент, перезаписав его, но для элементов списка у нас есть те же операции, что и над простыми переменными данного типа, например:
    cargo_weights = [15, "0.2", 10]
    cargo_weights[0] += 5
    print(cargo_weights) #Вывод:  [20, "0.2", 10]
    Или же можно вместо строки "0.2" положить в элемент числовое значение:
    cargo_weights = [15, "0.2", 10]
    cargo_weights[1] += int(cargo_weights[1])
    print(cargo_weights) #Тип первого элемента("0.2") изменился. Вывод:  [20, 0.2, 10]
  • Кроме того, в уже существующий список можно добавлять новые элементы, для этого служит ключевое слово(метод) append(), например в нашей программе перевозок появился новый продукт который нужно перевести в контейнере с телевизором, микроволновкой и телефоном:
    # объявляем контейнер
    cargo_container = ["LG v 7", "Samsung 10s", "Samsung ME83X"]
    cargo_weights = [15, 0.2, 10]
    # добавляем в него еще и наушники
    cargo_container.append("Headphones Sony s11") # добавили наименование
    cargo_weights.append(0.2) # добавили вес
    print(cargo_container) # Вывод: ["LG v 7", "Samsung 10s", "Samsung ME83X", "Headphones Sony s11"], теперь элементов 4, а последний элемент имеет индекс 3
    print(cargo_weights) # Вывод: [15, 0.2, 10, 0.2]
  • Элементы в списках могут повторяться, потому в предыдущем примере, список [15, 0.2, 10, 0.2] с двумя одинаковыми значениями - это нормально.
  • Над списками можно также проводить операции сложения и умножения:
    cargo_container = ["LG v 7", "Samsung 10s", "Samsung ME83X"]
    new_cargos = ["Headphones Sony s11", "Apple watch 7"]
    cargos = new_cargos + cargo_container
    print(cargos) # Вывод: ['Headphones Sony s11', 'Apple watch 7', 'LG v 7', 'Samsung 10s', 'Samsung ME83X']
    В списке-результате всех грузов cargos сначала будут элементы из списка new_cargos а потом элементы из cargo_container, потому что в таком порядке мы сказали складывать: cargos = new_cargos + cargo_container
  • Операция умножения:
    clear_list = [0] * 10
    print(clear_list) # Вывод: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    clear_list[0] = 1
    print(clear_list) # Вывод: [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  • Кроме того, можно элементы и выбрасывать из списка. Метод pop() списка позволяет получить и выкинуть из списка последний по индексу элемент в нем, например:
    cargo_weights = [15, 0.2, 10]
    microwave_weight = cargo_weights.pop()
    print(microwave_weight) # Вывод: 10  
    print(cargo_weights) # Вывод: [15, 0.2]
    Можно выкинуть из списка элемент с конкретным индексом, передав в метод pop() индекс ненужного элемента:
    cargo_weights = [15, 0.2, 10]
    phone_weight = cargo_weights.pop(1)
    print(phone_weight) # Вывод: 0.2 
    print(cargo_weights) # Вывод: [15, 10]
  • Про распаковку. То, что мы делали в самом начале:
    cargo_TV = "LG v 7"
    cargo_phone = "Samsung 10s"
    cargo_microwave = "Samsung ME83X"
    # упаковываем в лист, чтобы дальше было проще работать
    cargo_container = [cargo_TV, cargo_phone, cargo_microwave]
    Называется упаковкой элементов в список. Если их можно упаковывать, значит можно и распаковывать:
    cargo_TV, cargo_phone, cargo_microwave = cargo_container
    print(cargo_TV) # "LG v 7"
    print(cargo_phone) # "Samsung 10s"
    print(cargo_microwave) # "Samsung ME83X"
    Таким образом, мы распаковали наш список, и все значения в нем распределили на разные элементы. Причем старый список никуда не делся, мы все еще можем его использовать.
  • Обращение к последнему элементу списка. Обращаться к элементу списка можно не только по индексу с 0-го элемента и до последнего, но и в реверсивном порядке: от последнего до первого:
    animals = ["dog", "cat", "pigeon"]
    pigeon = animals[-1] # получили последний элемент коллекции: pigeon
    cat = animals[-2] # получили второй с конца элемент: cat

Списки - это одна из самых популярных структур данных в Python, если умно подходить к их использованию, то они становятся одним из мощнейших инструментов группировки данных.

  • Tuple(ru: кортеж) - еще одна коллекция, по функциям похожа на список, однако в отличаи от списка, является неизменяемой. Объявляется следующим образом:
    departments = ("developing", "sales", "QA")
    Объявление происходит через круглые скобки, и после объявления, изменить кортеж нельзя. Попытка присвоения другого значения одному из элементов, приведет к ошибке:
    departments = ("developing", "sales", "QA")
    departments[0] = 'management'
    
    # Ошибка:
    # Traceback (most recent call last):
    #   File "<stdin>", line 1, in <module>
    # TypeError: 'tuple' object does not support item assignment
  • Кортежи, как и списки можно распаковать:
    names = ("Vika", "Volha")
    name_vika, name_volha = names
  • Кортежи используются, когда необходима гарантия о неизменности данных.
  • Set(ru: множество) - коллекция, в которой все элементы должны быть уникальными:

    user_ids = {1,2,3}

    Id пользователей должны быть уникальны, потому, чтобы не писать такие проверки на уникальность, можно отдать эту работу коллекции множество.

  • Добавляются элементы с помощью метода add():

    user_ids.add(4)
    print(user_ids) # {1, 2, 3, 4}

    Но, если попробовать добавить в множество уже существующий элемент:

    user_ids.add(3)
    print(user_ids) # {1, 2, 3, 4}

    То множество не запишет элемент дважды.

  • Во множестве элементы не пронумерованы, потому получить доступ к элементу по индексу нельзя:

    employees_set = {"developer", 'QA', 'sales'}
    employees_set[0] # так делать нельзя, вызовет ошибку
    
    # Traceback (most recent call last):
    #     File "<stdin>", line 1, in <module>
    # TypeError: 'set' object is not subscriptable
  • Удалять элементы можно при помощи метода remove, этому методу нужно передать в качестве аргумента значение, которое мы хотим удалить из множества:

    user_ids.remove(3)
    print(user_ids) # {1, 2, 4}
    employees_set = {"developer", 'QA', 'sales'}
    employees_set.remove('QA')
    print(employees_set) # {"developer", 'sales'}
  • Над множествами также можно выполнять особые действия. Эти действия пришли из дискретной математики, а именно из теории множеств и позволяют проводить над данными во множествах специальные операции.

  • Разность - операция, результатом которой является новое множество, состоящее из элементов, которые есть в первом множестве, но нет во втором. В Python выполняется с помощью -:

    developers_ids = {1, 2, 3, 4, 5}
    managers_ids = {3, 4, 5, 6, 7}
    all_ids = developers_ids - managers_ids
    print(all_ids) # {1, 2}

    В переменной all_ids содержится множество, состоящее из всех элементов, которые есть только во множестве {1, 2, 3, 4, 5}, но нет во множестве {3, 4, 5, 6, 7}.

  • Объединение - операция, результатом который является новое множество, состоящее из элементов, которые есть хотя бы в одном множестве. В Python выполняется с помощью специального операнда |:

    developers_ids = {1, 2, 3, 4, 5}
    managers_ids = {3, 4, 5, 6, 7}
    all_ids = developers_ids | managers_ids
    print(all_ids) # {1, 2, 3, 4, 5, 6, 7}

    В переменной all_ids содержится множество, состоящее из всех уникальных элементов исходных множеств: {1, 2, 3, 4, 5}, {3, 4, 5, 6, 7}.

  • Пересечения - операция, результатом который является новое множество, состоящее из элементов, которые есть и в первом, и во втором множествах. В Python выполняется с помощью специального операнда &:

    developers_ids = {1, 2, 3, 4, 5}
    managers_ids = {3, 4, 5, 6, 7}
    all_ids = developers_ids & managers_ids
    print(all_ids) # {3, 4, 5}

    В переменной all_ids содержится множество, состоящее из всех уникальных элементов которые есть и в первом, и во втором множествах: {1, 2, 3, 4, 5}, {3, 4, 5, 6, 7}.

  • Симметрическая разность - операция, результатом который является новое множество, состоящее из элементов, которые есть в первом или во втором множествах, но не во обоих сразу. В Python выполняется с помощью специального операнда ^:

    developers_ids = {1, 2, 3, 4, 5}
    managers_ids = {3, 4, 5, 6, 7}
    all_ids = developers_ids ^ managers_ids
    print(all_ids) # {1, 2, 6, 7}

    В переменной all_ids содержится множество, состоящее из всех уникальных элементов которые есть в первом {1, 2, 3, 4, 5} или во втором {3, 4, 5, 6, 7} множествах, но не в обоих сразу. Эту операцию можно также представить как разность объединения и пересечения двух множеств:

    developers_ids = {1, 2, 3, 4, 5}
    managers_ids = {3, 4, 5, 6, 7}
    all_ids = (developers_ids | managers_ids) - (developers_ids & managers_ids)
    print(all_ids) # {1, 2, 6, 7}
  • Множества в Python отличаются широким списком различных операций над ними, упрощающих разработку. Если правильно использовать множества, это также может стать мощным инструментом оптимизации разработки и своих ресурсов.

  • Dictionary(ru: словарь) - это структура данных, предоставляющая доступ к данным по ключу. Является одной из реализаций ассоциативного массива.

  • Ключ - какое-то значение, с которым можно сопоставить другое значение, например имя и сам человек. Чтобы найти человека в толпе, нужно выкрикнуть его имя, только в программировании предполагается, что в толпе у всех разные имена. Такую че аналогию можно провести и с переменными, чтобы получить значение в переменной, нам нужно использовать ее название.

  • Инициализация похожа на инициализацию множества, только добавляются ключи:

    name_age_mapper = {"Alex": 22}

    При инициализации сначала указывается ключ, в нашем случае "Alex", потом двоеточие : и само значение. Для инициализации используется символ фигурных скобок.

  • Словари могут содержать несколько элементов сразу:

    name_age_mapper = {"Alex": 22, "Alice": 27, "Bob": 19}

    Как и множества, словари не имеют порядка. В них все элемента разбросаны. Гарантируется только одно: в словаре всегда будет однозначное соответствие ключа и значения.

  • Получение значения в словаре происходит так же, как и в листе, через квадратные скобки:

    alex_age = name_age_mapper["Alex"]
    print(alex_age) # 22

    В квадратные скобки нужно передавать ключ, а сама операция возвращает значение.

  • Ключом может быть любой хешируемый тип данных. Для определения хешируемости данных в Python есть встроенная функция hash, с ее помощью можно проверять можно ли использовать определенный тип данных, как ключ в словаре, например:

    hash(3) # 3
    hash("Alex") # 4978323820205119480
    hash([1,2,3]) # будет ошибка
    # Traceback (most recent call last):
    #     File "<stdin>", line 1, in <module>
    # TypeError: unhashable type: 'list'

    То есть можно создать следующие словари:

    int_key_dict = {1: "one", 2: "two"}
    print(int_key_dict[1]) # "one"
    
    str_key_dict = {"one": 1, "two": 2}
    print(str_key_dict["one"]) # 1

    Это будет корректно, но создать такой:

    list_key_dict = {[1,2]: "one, two"}
    # Traceback (most recent call last):
    #     File "<stdin>", line 1, in <module>
    # TypeError: unhashable type: 'list'

    не получится, будет ошибка. Из всех коллекций ключами в словарях могут быть только кортежи, следующая структура корректна:

    tuple_key_dict = {(1, 2): "one, two"}
    print(tuple_key_dict[(1,2)]) # "one, two"
  • Если ключами могут быть только хешируемые типы данных, то на значения нет ограничений. Ими могут быть строки, числа, були, другие коллекции, в том числе и другие словари:

    user_info = {
        "is_active": True, # буль
        "id": 1, # число
        "name": "Alex", # строка
        "permissions": [ 
            "read", "write", "execute" # список
        ],
        "job_info": { # словарь
            "position": "developer",
            "experience": 3,
            "stuff_and_number": {
                "monitors": 2,
                "mouse": 1,
                "laptops" : 2
            }
        }
    }
    print(user_info["job_info"]["stuff_and_number"]["monitors"]) # получили количество мониторов, оформленное на сотрудника - 2
    print(user_info["permissions"]) # получили разрешения пользователя: ["read", "write", "execute"]
    print(user_info["permissions"][0]) # получили первое разрешение пользователя: "read"
    user_info["permissions"].append("delete") # добавили в разрешения "delete"
    print(user_info["permissions"]) # получили разрешения пользователя: ["read", "write", "execute", "delete"]
  • Кроме получения элементов, в словарях можно присваивать и добавлять новые значения ключам. Делается это через доступ к элементу по ключу и операцию присвоения:

    users_age_mapper = {"Alex": 22, "Bob": 13}
    users_age_mapper["Bob"] = 20
    print(users_age_mapper) # {"Alex": 22, "Bob": 20}

    В предыдущем примере мы изменили значение ключа "Bob", теперь оно 20. Попробуем добавить кого-нибудь в словарь. Эта операции выглядит точно так же как и присвоение, однако, если такого ключа в словаре не было, то он просто добавится со значением, которое мы ему присвоили:

    users_age_mapper["Alice"] = 30
    print(users_age_mapper) # {"Alex": 22, "Bob": 20, "Alice": 30}
  • Выкидывать элементы из словаря можно при помощи метода "pop()", точно так же как и в листе, только в данном случае обязательно нужно передать ключ:

    users_age_mapper = {"Alex": 22, "Bob": 13}
    users_age_mapper.pop("Alex")
    print(users_age_mapper) # {"Bob": 13}
  • В словарях есть специальные функции для получения только ключей или только значений. Для получения только ключей словаря используется метод keys(), полученное значение желательно привести к удобной коллекции:

    users_age_mapper = {"Alex": 22, "Bob": 13}
    users_names = list(users_age_mapper.keys())
    print(users_names) # ["Alex", "Bob"]
  • Для получения всех значений используется метод values:

    users_age_mapper = {"Alex": 22, "Bob": 13}
    users_ages = list(users_age_mapper.values())
    print(users_ages) # [22, 13]
  • Также, можно собрать две коллекции в один словарь, с помощью встроенной функции zip, результат функции тоже сразу нужно привести к dict:

    names_keys = ["Alex", "Bob"]
    ages_values = [22, 13]
    users_age_mapper = dict(zip(names_keys, ages_values)) # {"Alex": 22, "Bob": 13}
  • Так же есть инструмент для получения ключей и значений в виде кортежа. Специальный метод items() возвращает все ключи и значения в виде коллекции кортежей, результат метода так же лучше привести к списку:

    users_age_mapper = {"Alex": 22, "Bob": 13}
    print(list(users_age_mapper.items())) # [("Alex", 22), ("Bob", 13)]

    Это бывает полезно, когда нужно быстро получить доступ к ключу и значению стразу, используя распаковку коллекций:

    users_age_mapper = {"Alex": 22, "Bob": 13}
    name, age = list(users_age_mapper.items())[0]
    print(name) # 'Alex"
    print(age) # 22
  • Иногда бывает такое, что какого-то ключа нет в словаре. Доступ по несуществующему ключу вызовет исключение:

    users_age_mapper = {"Alex": 22, "Bob": 13}
    print(users_age_mapper["Alice"]) # будет ошибка
    # Traceback (most recent call last):
    #     File "<stdin>", line 1, in <module>
    # KeyError: 'Alice'

    Чтобы ошибок не было, предусмотрен специальный метод get(), в который передается ключ и значение по умолчанию. Если такого ключа в словаре нет, то метод вернет значение по умолчанию и не вызовет ошибки. По умолчанию, значение по умолчанию равно None:

    users_age_mapper = {"Alex": 22, "Bob": 13}
    alices_age = users_age_mapper.get("Alice") # ошибки нет
    print(alices_age) # None
    alices_age = users_age_mapper.get("Alice", 0) # добавили значение по умолчанию
    print(alices_age) # 0
  • (extra)Словари являются самой важной структурой данных в Python, вся память и программа построена по образу словаря в Python, например мы можем получить доступ ко всем доступным переменным в нашей программе через специальную функцию locals:

    users_age_mapper = {"Alex": 22, "Bob": 13}
    print(locals()) # {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'users_age_mapper': {'Alex': 22, 'Bob': 13}}

    И даже больше, мы можем влиять на ход программы через эту функцию, например создать еще одну переменную:

    locals()["age"] = 5
    print(age) # 5

    Причем до этого переменная age не объявлялась. Это тот самый случай когда строковые названия могут перейти в названия переменных.

  • Словари - это ассоциативные массивы в Python. В них отсутствует нумерация элементов, однако значения можно получить по специальному ключу. Так же они предоставляют специальные инструменты для работы с ними.

  • Коллекции можно приводить друг к другу, используя фишки разных коллекций для облегчения работы, например:
    users_ids = [1,1,1,2,3,4,5,6,6] # id юзеров должны быть уникальны, программа должна это гарантировать
    users_ids_set = set(users_ids) # здесь мы избавляемся от повторений и получаем множество {1,2,3,4,5,6}
    # однако, превратив список во множество, мы потеряли возможность получать элемент по индексу.
    # для удобной работы с данными можно привести множество обратно к списку
    users_ids = list(users_ids_set) # теперь у нас список уникальных id: [1,2,3,4,5,6]
    # далее, если нам нужно список превратить в неизменяемый кортеж, можно так же привети его к кортежу
    users_ids = tuple(users_ids) # получили неизменяемый кортеж уникальных элементов
    Действие в предыдущем примере можно было уместить в одну строку:
    users_ids = [1,1,1,2,3,4,5,6,6]
    users_ids = tuple(list(set(users_ids))) # получили неизменяемый кортеж уникальных id: (1,2,3,4,5,6)
  • Получение количества элементов любой коллекции. Для получения количества элементов коллекции, используется функция len():
    employees = ["Alex", "Siarhey", "Hanna"]
    employees_number = len(employees) # 3 сотрудника
    
    users_ids = {1,2,3}
    ids_number = len(users_ids) # 3 уникальных id
    
    user_age_mapper = {"Alex": 22, "Bob": 13}
    users_number = len(user_age_mapper) # 2 пользователя занесены в словарь
  • В коллекции можно упаковывать любые данные в том числе и другие коллекции.
  • Распаковывать можно следующие коллекции: списки, множества, картежи. Словари тоже можно распаковывать, но по-другому.
  • Python - это язык со ссылочной моделью данных. Это значит, что все в Python - это ссылка. Занося значение в переменную, мы, на самом деле, заносим в эту переменную ссылку на это значение. Проще всего это представить как коробу(какое-то значение) и наклейку на эту коробку(переменная). Наклеек может быть много, а коробка будет одна.
  • Встроенная функция id() возвращает адрес значения в памяти, например:
    count = 5
    print(id(count)) # 9785024
    numb = 5
    print(id(numb)) # 9785024
    numb += 1
    print(id(numb)) # 9785056
    Предыдущий пример показывает, что на одни и те же значения указывают одни и те же ссылки(у них одни и те же адреса в памяти). Это особенность базовых типов(int, float, str, bool), для них в памяти уже зарезервированы места, потому ссылки на них будут одни и те же в контексте одной программы.
  • Однако, всю суть ссылочных моделей видно с более сложными типами данных, например, коллекциями:
    employees = ["Alex", "Nika", "Dora"]
    print(id(employees)) # 140608816168640
    
    people = ["Alex", "Nika", "Dora"]
    print(id(people)) # 140608816133568
    Значения в переменных абсолютно равны. Однако, в данном случае переменные, указывающие на одни и те же значения в памяти, имеют разные адреса. Это все происходит потому, что для сложных структур данных, типа коллекций, каждый раз при инициализации создается новое значение в памяти.
  • Но ссылочная модель все еще работает. Перепишем предыдущий пример иначе:
    employees = ["Alex", "Nika", "Dora"]
    print(id(employees)) # 140608816168640
    
    people = employees
    print(id(people)) # 140608816168640
    Теперь переменные указывают на одно и то же место в памяти. Почему так? Потому что список people не был повторно инициализирован, как в предыдущем примере. Инициализация списка происходит при помощи квадратных скобок: [].
  • Попробуем поменять значения списка people:
    employees = ["Alex", "Nika", "Dora"]
    people = employees
    
    people[0] = "Vika"
    
    print(people) # ['Vika', 'Nika', 'Dora']
    print(employees) # ['Vika', 'Nika', 'Dora']
    При изменении списка people, так же поменялся и список employees, потому что две эти переменны указывают на одни и те же данные в памяти. Незнание этой особенности языка, может допустить множество логических ошибок, при написании кода, когда значения, которые вроде как не должны были меняться, имеют совершенно другие значения, чем ожидалось.
  • Для того, чтобы избежать такой проблемы связанных с ссылочной моделью, необходимо создавать новые коллекции на основе уже существующих, делается это с помощью функции list, как и в приведении типов:
    employees = ["Alex", "Nika", "Dora"]
    people = list(employees) # инициализировали новый список, на основе существующего
    
    print(id(employees)) # 140608805220672
    print(id(people)) # 140608804748160
    
    people[0] = "Vika"
    
    print(people) # ['Vika', 'Nika', 'Dora']
    print(employees) # ['Alex', 'Nika', 'Dora']
    При инициализации на основе уже существующего списка, адреса, находящиеся в переменных, стали разными. Кроме того, изменения списка people не повлияли на список employees.
  • (extra) Так же стоит быть осторожными в случае объявления списка списков с уже заготовленными значениями, которые в течении программы могут меняться. Чаще всего это делается через умножение. Но у этого есть последствия. Например:
    matrix = [[0]] * 10
    print(matrix) # [[0], [0], [0], [0], [0], [0], [0], [0], [0], [0]]
    matrix[0][0] = 1 # Поменяем значение первого столбца, первой строки
    print(matrix) # [[1], [1], [1], [1], [1], [1], [1], [1], [1], [1]]
    В данном случае опять же работает ссылочная модель, потому что [[0]] - уже проинициализированный список, и, умножая его на 10, мы просто десять раз повторяем ссылку на одно и то же место в памяти. Правильный путь инициализировать список списков в данном случае, это использовать List Comprehension.
  1. Что такое коллекция?
  2. Какие коллекции вы знаете? В чем разница между ними?
  3. Какие операции можно совершать над множествами? В чем разница между ними?
  4. Как хранятся данные во множестве?
  5. Для каких случаев подходят кортежи, для каких множества, для каких списки?
  6. Что такое словарь?
  7. Как происходит доступ к данным в словаре?
  8. Какие данные подходят для ключей в словарях?
  9. Как устроена модель данных в Python?
  10. Что произойдет при выполнении следующего кода?
    names_age_mapper = {"Alex": 22, "Bob": 20}
    users_names = names_age_mapper
    
    users_names.pop("Alex")
    names_age_mapper["Alice"] = 30
    
    print(users_names["Alice"])
    print(names_age_mapper["Alex"])