{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Web-scraping: сбор данных из баз данных и интернет-источников\n", "\n", "*Алла Тамбовцева, НИУ ВШЭ*\n", "\n", "## Списки, цикл `for` и условные конструкции" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Создадим список из целых чисел и назовём его `numbers`:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "numbers = [1, 0, 3, -5, 9, -8, -10, -12, 3, 7, 8]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Напишем код, который будет проверять, положителен ли первый элемент списка или нет. Создадим для этого условную конструкцию `if-else`. " ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Yes\n" ] } ], "source": [ "if numbers[0] > 0:\n", " print(\"Yes\")\n", "else:\n", " print(\"No\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В части с `if` мы прописываем условие, в зависимости от которого Python будет делать выбор, что выводить на экран, а после двоеточия перечисляем действия, которые будут выполняться в случае, если элемент удовлетворяет условию. В части с `else` мы уже не пишем никакого условия – оператор `else` сам по себе означает «в случае, если условие в выражении с `if` не выполнено».\n", "\n", "Часть с `else` является необязательной: программа может существовать только с условием `if`. Тогда в случае невыполнения условия ничего происходить не будет, Python просто перейдет к следующим строкам кода." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# то же условие для второго элемента – ничего\n", "\n", "if numbers[1] > 0:\n", " print(\"Yes\")" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Yes\n" ] } ], "source": [ "# для первого элемента – то же, что с else\n", "\n", "if numbers[0] > 0:\n", " print(\"Yes\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь поставим перед собой такую задачу: распределить все элементы списка `numbers` на два списка, список `negatives` из отрицательных чисел и список `non_negatives` из неотрицательных чисел. Помимо условия воспользуемся циклом `for` и методом `.append()`, который добавляет элемент в конец списка." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "negatives = []\n", "non_negatives = []\n", "\n", "for i in numbers:\n", " if i < 0:\n", " negatives.append(i)\n", " else:\n", " non_negatives.append(i)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Сначала создадим два пустых списка `negatives` и `non_negatives`, в них мы будем записывать соответствующие элементы из списка `numbers`. \n", "\n", "Как устроен цикл `for` выше? Кодом выше мы доносим до Python мысль: пробегайся по всем элементам списка `numbers` (`for i in numbers`), проверяй для каждого элемента выполнение условия (`if i < 0`) и записывай этот элемент либо к одному списку, либо к другому. \n", "\n", "Вообще любой цикл `for` имеет такую структуру: сначала указывается, по каким значениям нужно пробегаться, а потом, что нужно делать. Действия, которые нужно выполнить в цикле, указываются после двоеточия в `for` – эта часть назвается телом цикла.\n", "\n", "Буквы в конструкции `for` могут быть любые, совсем необязательно брать букву `i`. Python сам поймет, что мы имеем в виду, запуская цикл. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Посмотрим, как выглядят списки `negatives` и `non_negatives`:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[-5, -8, -10, -12]\n", "[1, 0, 3, 9, 3, 7, 8]\n" ] } ], "source": [ "print(negatives)\n", "print(non_negatives)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Значения добавляются в списки последовательно, в том порядке, в котором они идут в исходном списке `numbers`. Если мы хотим их отсортировать, нам понадобится метод `.sort()`:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[-12, -10, -8, -5]\n", "[0, 1, 3, 3, 7, 8, 9]\n" ] } ], "source": [ "negatives.sort()\n", "non_negatives.sort()\n", "\n", "print(negatives)\n", "print(non_negatives)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "«Наслаивать» методы на списках друг на друга нельзя, их нужно применять последовательно, посколько они (за редким исключением, например, методы `.index()` или `.count()` ) ничего не возвращают, а сразу модифицируют список. Так, метод `.append()` добавляет элемент в конец списка, но при этом возвращает пустой объект `None`:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "None\n" ] } ], "source": [ "# попробуем сохранить результат .append в переменную\n", "# и получим пустоту и безысходность...\n", "\n", "L = [1, 0, 3]\n", "L2 = L.append(8)\n", "print(L2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "К слову, если методы не изменяют исходный элемент, а возвращают его модифицированную копию, использовать несколько методов сразу можно. В частности, это актуально для строк. Попробуем сначала сделать все буквы в тексте заглавными, а потом разбить его на части по пробелу – всё одной строкой кода:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['A', 'B', 'C']" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"a b c\".upper().split()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Применить методы в другом порядке в данном случае не получится: если мы сначала применим `.split()`, результатом будет список, а метод `.upper()` существует только для строк:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "ename": "AttributeError", "evalue": "'list' object has no attribute 'upper'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;34m\"a b c\"\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msplit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mupper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m: 'list' object has no attribute 'upper'" ] } ], "source": [ "\"a b c\".split().upper()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Кортежи и словари" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Кортежи встречаются не только в программировании, но и в математике. В математике под кортежем обычно понимают упорядоченную совокупность элементов, то есть совокупность, порядок элементов которой фиксирован. В кортеже мы точно знаем, какой элемент является первым, какой – вторым, и так далее.\n", "\n", "Внешне кортежи несильно отличаются от списков. Единственное внешнее отличие – элементы кортежа заключаются в круглые, а не в квадратные скобки. " ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "# список (list)\n", "L1 = [8, 9, 0]\n", "\n", "# кортеж (tuple)\n", "T1 = (8, 9, 0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Но, несмотря на кажущееся сходство, кортежи и списки – принципиально разные объекты. Главное отличие кортежей от списков заключается в том, что кортежи – неизменяемые объекты. Другими словами, изменять элементы кортежа нельзя. Проверим:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[8, 90, 0]" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# список – все ок\n", "\n", "L1[1] = 90\n", "L1" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "'tuple' object does not support item assignment", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0mT1\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;36m8\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m9\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0mT1\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m90\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: 'tuple' object does not support item assignment" ] } ], "source": [ "# кортеж – нельзя\n", "\n", "T1 = (8, 9, 0)\n", "T1[1] = 90" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если посмотреть на методы, применяемые к кортежам и спискам (например, набрать `T1.`или `L1.` и нажать *Tab*), то можно заметить, что методов для кортежей сильно меньше по сравнению с методами для списков. Во многом это связано с тем, что кортеж нельзя изменить. \n", "\n", "Иногда это свойство бывает полезным (некоторая «защита» от изменений), иногда – не очень, но для нас пока важно познакомиться с разными объектами в Python, чтобы потом не удивляться. Ведь многие более продвинутые функции могут возвращать результат или, наоборот, принимать на вход только кортежи или только списки." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь немного поговорим о словарях. Обсуждая словари в Python, удобно проводить аналогию с обычными словарями (бумажными или электронными). Что такое словарь? Перечень из пар: слово-значение или слово-список значений, если значений несколько. Вот и словарь в Python – это объект, структура данных, которая позволяет хранить пары соответствий. Создадим маленький словарь с именами студентов и их оценками:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "d = {\"Anna\" : 10, \"Peter\" : 9} " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Можем запросить перечень ключей этого словаря:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "dict_keys(['Anna', 'Peter'])" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "d.keys() " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Или перечень значений:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "dict_values([10, 9])" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "d.values() " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Или сразу всё в виде упорядоченных пар, на первом месте – ключ, на втором – значение:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "dict_items([('Anna', 10), ('Peter', 9)])" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "d.items() " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь попробуем вызвать какое-нибудь значение по его ключу, это понадобится нам в дальнейшем для выгрузки информации из веб-страниц или из API. Как и в случае со списками или кортежами, к элементам словаря можно обращаться, используя квадратные скобки. Только выбор элемента производится не по индексу, а по ключу: сначала указываем название словаря, а потом в квадратных скобках – ключ, по которому мы хотим вернуть значение. Например, выясним оценку Анны:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "d[\"Anna\"] " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "А что будет, если мы попробуем вызвать значение по несуществующему ключу?" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "ename": "KeyError", "evalue": "'Liz'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0md\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"Liz\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mKeyError\u001b[0m: 'Liz'" ] } ], "source": [ "d[\"Liz\"]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Что ожидаемо, мы получим ошибку. Теперь представьте себе такую ситуацию: у нас есть список студентов (ключей) и мы хотим в цикле вернуть по ним их оценки (значения). Какого-то одного из студентов в словаре нет. Что произойдет? На каком-то этапе Python выдаст ошибку, мы вывалимся из цикла, и на этом наша работа остановится. Обидно, да? Чтобы такого избежать, получать значение по ключу можно другим способом – используя метод `.get()`:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "d.get(\"Liz\") # ни результата, ни ошибки" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "d.get(\"Anna\") # а здесь всё как обычно" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "На этом мы пока закончим знакомство со структурами данных в Python, некоторые полезные сюжеты будем обсуждать по ходу курса, чтобы не было информационного перегруза." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 2 }