[МУЗЫКА] [МУЗЫКА] Теперь перейдем к изучению нового типа данных, который называется список. Если вы знаете какие-то другие языки программирования, то вам, может быть, более знакомо название «массив». Собственно, в Python массив называется списком, а то, что называется списком в других языках, в Python никак не называется. Это просто набор каких-то элементов, очень похож на кортеж, но отличается от него тем, что можно взять и поменять значение какого-то элемента. То есть это первый изменяемый тип, который мы изучаем. С этим будет связано много интересного. Итак, как создать константный список? Почти, как кортеж, используя квадратные скобки. При этом, в отличие от кортежа, квадратные скобки опускать нельзя, потому что иначе оно будет интерпретировано как кортеж. Собственно, что можно делать со списком? Читать от него длину, обращаться к элементам по индексу как с положительной, так и с отрицательной нумерацией — в общем-то, большинство операций, которые можно сделать с кортежем или со строкой. Давайте я вам назову как-нибудь покороче, потому что сейчас будет много всего. Итак, напомню, что у нас любая переменная в Python является всего лишь ссылкой на какой-то объект. Раньше у нас все объекты были не изменяемые, и когда мы писали «переменная равна какому-то новому значению», у нас создавался новый объект в памяти, и ярлычок, ссылка, просто привязывался к новому объекту. Если мы напишем, что у нас некоторая переменная равна списку, то что произойдет? У вас просто установится еще одна ссылка, у меня она называется — b, которая будет показывать на абсолютно тот же самый объект, на который показывает a, и из-за того, что объект изменяемый, у нас такого раньше не было, произойдут забавные спецэффекты. Ну, во-первых, в списке мы можем что-то поменять. То есть мы можем один конкретный элемент взять и изменить. То есть конструкция вида a [0] — это нулевой элемент, пока что 1, может быть слева от присваивания. Мы можем обращаться с ней, как с переменной. И, например, мы положим туда 4. Давайте напечатаем список a. Убедимся, что действительно работает. Да, поменялся элемент. А теперь посмотрим, а что произошло со списком b? Мы туда положили список 1, 2, 3, но при выводе опять печатаются 4, 2, 3. То есть если мы меняем a, то меняется и b. Почему? Потому что a и b — всего лишь ссылки на один и тот же объект. Если мы меняем объект, то, естественно, по любой ссылке мы получим измененный объект. Вот это такая штука, которая мешает нам делать копии списков. Ну, простой способ сделать копию списка, новый объект, это сделать срез, содержащий все элементы списка, то есть написать [ : ]. Когда мы делаем срез, у нас в любом случае генерируется, создается новый объект в памяти. То есть сейчас, если мы напечатаем b, то у нас будет объект 1, 2, 3, список не измененный, такой, какой он был на момент копирования. То есть когда мы делаем любой срез, у нас создается копия объекта. Пока они были не изменяемые, она оставалась без изменений, возможно, это физически был один объект. Мы это даже толком не знали, и, в общем-то, это ни на что не влияло. Теперь, когда объекты изменяемые, у нас есть два изменяемых объекта в памяти. Один из них мы меняем, второй не меняется, то есть у вас ваши ссылки привязаны к разным объектам. Что произойдет, если мы сделаем просто два одинаковых по содержимому списка? То есть мы напишем явно в виде a = 1 2 3 и b = 1 2 3 и из них a поменяем. Напомню, что если объекты не изменяемые, то, в принципе, могло оказаться так, что у вас две ссылки будут показывать на один и тот же объект, потому что в памяти уже есть, и чтобы не плодить сущности в памяти, они просто привязывались к одному и тому же объекту. Если же объект изменяемый, то у вас обязательно создается копия этого объекта в памяти. То есть эти объекты разные, несмотря на то, что содержат в себе одинаковые элементы. Запускаем. Смотрим: и действительно b не изменился. То есть объекты оказались разные. Из какого типа объектов состоят списки? Единственный допустимый тип данных для списка — это ссылка. То есть абсолютно все списки, которые есть в Python, состоят из ссылок на какие-то объекты. Например, у нас может быть список, состоящий из достаточно разных по природе объектов. Например, из кортежа, который состоит из кортежа какого-то сложного и так далее. То есть все может быть записано вперемешку. В принципе, внутри списка может находиться и другой список, и это тоже нормально, то есть любой объект является ссылкой. Давайте все это напечатаем. Проверим, что оно хотя бы не вызывает никаких ошибок при запуске. Да. Все хорошо. В общем-то, как мы писали, так оно и напечатано. Главное, что нужно знать, это, что абсолютно любой объект — это ссылка, и когда мы отсчитываем по индексу что-то или меняем, или выводим, или используем как-то, мы отсчитываем ровно такую по счету ссылку. Посмотрим, что можно еще хорошего сделать со списком. Допустим, что произойдет, если передать список в функцию в качестве параметра? Давайте сделаем какую-нибудь функцию, которая будет заменять первый элемент списка на число 0. Вот такая простая функция. Значит, один параметр у нее — собственно список, который будет. Нулевой элемент заменяем на 0. И давайте напечатаем, вызовем эту функцию сейчас. Создадим список, в который положим какие-то элементы. Функция ничего не возвращает. Она меняет список, переданный по указателю, по ссылке. Вызовем нашу функцию от созданного списка, а потом напечатаем этот список. Поменялся элемент. Все хорошо. Обратите внимание, что нельзя было просто взять и передать туда какой-то свежесозданный список. Ну, вернее, можно было бы, наверное. Сейчас посмотрим. Ну да. Передать можно было, но, естественно, он поменялся и уничтожился, потому что это был временный объект, который больше нигде не используется, что означает, что мы можем взять и поменять параметр переданной функции. В функцию передается ссылка на объект в качестве параметра. В нашем случае туда передавалась ссылка на список. Список является изменяемым объектом. Мы изменяем, собственно, этот объект, и, естественно, он изменяется везде. Ссылка остается прежней. А что произошло бы, если мы, например, хотели развернуть наш список, то есть написать функцию разворота списка и сделать вот такой финт ушами, а именно в нашу переменную, параметр по сути, положить развернутый список? И сделать это, напомню, мы можем с помощью среза, вот такого вот хитрого, как мы это делали со строчками. Вот оно уже подчеркивает. Ну давайте мы запустим. Посмотрим, что не так. А, ну пока что я функцию назвал плохо. Вот. Значит, хорошо. Оно, по крайней мере, работает, но не делает то, что должно делать. Почему? Потому что у нас происходит попытка изменить объект, переданный в функцию. То есть нам передалась туда ссылка. Если мы по ней прошли и внутри изменяемого объекта что-то поменяли — это нормально. Если же мы поменяли параметр функции, то это произошло только локально, как это происходило с локальными переменами раньше. То есть то, что передано в функцию, по-прежнему измениться не может. Какая передалась ссылка, такая и осталась ссылка в основной программе, если она была в качестве параметра, а не возвращена, например. Соответственно, внутри функции у нас ссылка изменилась, наша локальная переменная, создался какой-то временный список развернутый, и когда функция кончилась, он уничтожился. Все. В основной программе никаких изменений не произошло. То есть пройдя по ссылке на изменяемый объект, вы что-то можете изменить, а саму ссылку вы изменить не можете изнутри функции. Вот такая вот штука. Ну и, в принципе, нужно конечно не злоупотреблять изменением параметров, переданных в функцию. То есть это должно быть обоснованно и логично, понятно для людей, что если они передали какие-то данные в вашу функцию, а она взяла и что-то поменяла в них, то это, конечно, должно быть ожидаемо. Потому что чаще всего люди ожидают, что то, что они передают в функцию, не испортится. Было бы странно, например, если бы мы посчитали синус от какого-то числа, а у вас этот угол, от которого вы считали синус, взял и изменился. Со списками, конечно, это уже более ожидаемо, что если вы куда-то отдали список, то вы не можете гарантировать, что вам ничего не поменяют. Но функции нужно называть и описывать, комментировать так, чтобы читающему человеку было понятно, что она возьмет и поменяет свои параметры. Ну и конечно, вот такие конструкции, как мы сейчас пытались делать, делать нельзя. Это по-прежнему легко объясняется тем, что любой параметр, переданный в функцию, считается локальной переменной и уничтожается после окончания функции. Никаких изменений в основной программе не происходит. Посмотрим, как же можно было все-таки решить эту задачу. То есть нам нужно было как-то переставить по-настоящему эти элементы. Например, мы могли сделать это с помощью цикла for как-то или while, хитро придумать обмен элементов между собой, но в конце сегодняшнего занятия мы посмотрим несколько удобных функций, в том числе функцию разворота списка. [МУЗЫКА] [МУЗЫКА]