{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Изменяемые и неизменяемый типы объектов\n", "\n", "Рассмотрим ещё один пример.\n", "\n", "```python\n", "a = 0 # 1\n", "a += 1 # 2\n", "```\n", "Программисту `C/C++` может показаться, что во второй строке значение, хранящееся в целочисленном объекте созданном в первой строке, увеличивается на `1`. На самом деле это не так: создаётся новый объект, хранящий результат вычисления выражения `a + 1`, а затем `a` связывается с этим новым объектом. Это проще увидеть, переписав выражение `a += 1` в виде `a = a + 1`. " ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "140712650217200\n", "140712650217232\n" ] } ], "source": [ "a = 0\n", "print(id(a)) \n", "a += 1\n", "print(id(a))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "На самом деле, в `python` все типы объектов можно разделить на два вида: **изменяемые** (`mutable`) и **неизменяемый** (`immutable`). \n", "- Значение, хранящееся в объекте неизменяемого типа, невозможно изменить никакими способами. Единственный способ получить объект такого типа с другим значением --- создать новый объект. \n", "- Значение, хранящееся в объекте изменяемого типа, можно изменить не создавая новый объект. Изменения объекта возможно за счет вызова его методов или применения к нему операторов. \n", "\n", "Все встроенные числовые типы (`bool`, `int`, `float`, `complex`, (`decimal`, `fractions`, которые также присутствуют в стандартной библиотеке, но обсуждаться не будут)) являются неизменяемыми. Единственный изменяемый тип объектов, с которым мы уже сталкивались --- `list`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Разделяемый ссылки \n", "\n", "Изменяемость и неизменяемость играет наибольшую роль в случае, когда несколько имен ссылаются на один и тот же объект. \n", "\n", "### Разделяемые ссылки на неизменяемый объект\n", "\n", "Создадим две ссылки `a` и `b` на один и тот же объект целочисленного типа." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "a = 0\n", "b = a" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "Во второй строке создаётся имя `b` и оно связывается с объектом, на которое указывает имя `a`. Убедиться, что имена указывают на один и тот же объект, можно сравнив их адреса (`id(a) == id(b)`). В python помимо оператора сравнения `==`, ещё есть оператор сравнения на идентичность `is`, который по сути дела сравнивает ссылки на объекты." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "False\n" ] } ], "source": [ "print(a is b)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "```{figure} /_static/lecture_specific/mutable_immutable/ba0.svg\n", "```\n", "\n", "```{note}\n", "Имена всегда указывают на объекты. Имена не могут указывать на другие имена, но объекты могут содержать в себе ссылки на другие объекты.\n", "```\n", "\n", "Расширим предыдущий пример ещё двумя строками." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "a += 42" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```{figure} /_static/lecture_specific/mutable_immutable/ba0a42.svg\n", "```\n", "Создаётся объект `42` и имя `a` связывается с ним. Объект `0` не претерпел изменений (и не мог бы, `int` неизменяемый), имя `b` по прежнему связанно с ним." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "False\n", "84 0\n" ] } ], "source": [ "print(a is b) \n", "print(a, b) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Таким образом, если есть несколько ссылок на один и тот неизменяемый объект, можно не переживать, что изменяя объект по одной из них вы измените значения по остальным ссылкам.\n", "\n", "### Разделяемые ссылки на изменяемый объект\n", "\n", "Рассмотрим пример с изменяемым типом данных `list`. Если слева и справа от оператора `+=` стоит список, то в конец списка слева добавляются все элементы списка справа от оператора. Т.е. если список `l` --- список, то выражение \n", "```python\n", "l += v\n", "```\n", "эквивалентно выражению\n", "```python\n", "l.extend(v)\n", "```" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['a', 'b', 'c', 1, 2, 3]\n" ] } ], "source": [ "l = [\"a\", \"b\", \"c\"]\n", "l += [1, 2, 3]\n", "print(l)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "Создадим список и две ссылки на него." ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n" ] } ], "source": [ "l1 = [\"a\", \"b\", \"c\"]\n", "l2 = l1\n", "print(l1 is l2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Изменим список `l1`, добавив в него какой-нибудь элемент." ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['a', 'b', 'c', 1, 2, 3]\n" ] } ], "source": [ "l1 += [1, 2, 3]\n", "print(l1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Что произошло с списком `l2`?" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['a', 'b', 'c', 1, 2, 3]\n", "True\n" ] } ], "source": [ "print(l2) \n", "print(l1 is l2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Изменение объекта по имени `l1` отражается и на имени `l2`, так как они ссылаются на один и тот изменяемый объект. \n", "\n", "Оба имени `l1` и `l2` ссылаются на один и тот же изменяемый объект. Изменяемые объекты могут менять своё содержимое на месте. Тем не менее оператор присваивания `=` сохраняет смысл связывания имени и объекта." ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n", "False\n" ] } ], "source": [ "l1 = [\"a\", \"b\", \"c\"]\n", "l2 = l1\n", "print(l1 is l2)\n", "l1 = [1, 2, 3] \n", "print(l1 is l2) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## В качестве аргументов функций\n", "\n", "```{note}\n", "Строго говоря, понятия **аргументы** и **параметры** функции отличаются. Параметры --- переменные, которые указываются при объявлении функции, а аргументы --- то, что передаётся функции вместо параметров при её вызове. \n", "```\n", "\n", "Изменяемость/неизменяемость типа объекта играет большую при написании функций в python.\n", "\n", "### Передача аргументов в функцию в C/C++\n", "В `C/C++` функции могут принимать параметры двумя способами. \n", "```c++\n", "#include \n", "void f(int x, int &y){ // объявление функции, x и y --- её параметры\n", " x++;\n", " y++;\n", " return;\n", "}\n", "\n", "int main(){\n", " int a = 0, b=0;\n", " f(a, b); // вызов функции, a и b --- её аргументы\n", " std::cout << a << \" \" << b;\n", " return 0;\n", "}\n", "```\n", "1. `x` --- параметр функции `f`, передаваемый по **значению**. При вызове функции, создаётся локальная переменная `x`, её значение инициализируется значением переменной `a`. Изменение `x` никак не повлияет на значение переменной `a`. \n", "2. `y` --- параметр функции `f`, передаваемый по **ссылке**. При вызове функции, создаётся ссылка `y` на переменную `b` и изменение значения `y` затронет и значение переменной `b`. \n", "\n", "Кроме того, можно объявлять константные параметры функции с помощью ключевого слова `const`. Компилятор выдаст ошибку, если функция пытается изменить значение такого аргумента.\n", "\n", "### Передача аргументов в функцию в python\n", "\n", "В python отсутствуют концепции как передачи аргументов по ссылке или по значению, так и константности переменной/параметра в смысле `C/C++`. В качестве аргумента всегда передаётся ссылка на аргумент (объект), а возможность изменить исходный объект внутри функции зависит от того, какого он типа.\n" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "def f(a, b):\n", " a += b\n", " return a " ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x = 0, y = 42\n", "False\n" ] } ], "source": [ "x = 0\n", "y = f(x, 42)\n", "print(f\"{x=}, {y=}\")\n", "print(x is y)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "l1=['a', 'b', 'c', 1, 2, 3], l2=['a', 'b', 'c', 1, 2, 3]\n", "True\n" ] } ], "source": [ "l1 = [\"a\", \"b\", \"c\"]\n", "l2 = f(l1, [1, 2, 3])\n", "print(f\"{l1=}, {l2=}\")\n", "print(l1 is l2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "В примере выше одна и та же функция изменяет или не изменяет значение своего аргумента в зависимости от того, какого типа этот аргумент. Программист должен сам следить за такими деталями и при необходимости передавать в функцию копию объектов.\n", "\n", "```{note}\n", " Выражение `s + t` совершает конкатенацию списков `s` и `t` (создаёт новый объект). Выражение `s += t` добавляет элементы из списка `t` в список `s` (изменяет `s`). \n", "```\n", "\n", "### Создание копий объектов\n", "\n", "Чтобы гарантировать, что значения аргументов функции не изменятся, можно создать копию этих аргументов и далее пользоваться ими. За копирование объектов отвечает модуль [copy](https://docs.python.org/3/library/copy.html). В нем есть две функции копирования `copy.copy` и `copy.deepcopy`. Разница между этими функциями заметна только для объектов, которые содержат в себе другие объекты (`list`, например), но и то не всегда. Подробнее это отличие будет обсуждаться позже. \n", "\n", "Предположим, что нас не устраивает, что функция изменила список `l1` в предыдущем примере. \n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "l1=['a', 'b', 'c'], l2=['a', 'b', 'c', 1, 2, 3]\n", "False\n" ] } ], "source": [ "from copy import copy # <-----\n", "\n", "def f(a, b):\n", " a = copy(a) # <-----\n", " a += b\n", " return a \n", "\n", "l1 = [\"a\", \"b\", \"c\"]\n", "l2 = f(l1, [1, 2, 3])\n", "print(f\"{l1=}, {l2=}\")\n", "print(l1 is l2)" ] } ], "metadata": { "interpreter": { "hash": "eea3ef54c04064aa17c056cc2578c78db8b44278034b77b7225a3166c34cea02" }, "kernelspec": { "display_name": "Python 3.8.10 ('venv': venv)", "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.8.10" } }, "nbformat": 4, "nbformat_minor": 2 }