Неизменяемые и изменчивые типы

Я запутался в том, что такое неизменный тип. Я знаю float объект считается неизменным, на примере такого типа из моей книги:

class RoundFloat(float):
    def __new__(cls, val):
        return float.__new__(cls, round(val, 2))

Считается ли это неизменным из-за структуры / иерархии классов? float находится на вершине класса и является собственным вызовом метода. Подобно этому типу примера (хотя моя книга говорит dict изменчиво):

class SortedKeyDict(dict):
    def __new__(cls, val):
        return dict.__new__(cls, val.clear())

Принимая во внимание, что что-то непостоянное имеет методы внутри класса, с примером такого типа:

class SortedKeyDict_a(dict):
    def example(self):
        return self.keys()

Кроме того, для последнего class(SortedKeyDict_a), если я передам этот тип набора к нему:

d = (('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2))

без вызова example метод, он возвращает словарь. SortedKeyDict с __new__ помечает это как ошибку. Я пытался передать целые числа в RoundFloat класс с __new__ и это не помечено без ошибок.

19 ответов

Какие? Поплавки неизменны? Но я не могу сделать

x = 5.0
x += 7.0
print x # 12.0

Разве это не "мут" х?

Ну, вы согласны, что строки неизменны, верно? Но вы можете сделать то же самое.

s = 'foo'
s += 'bar'
print s # foobar

Значение переменной изменяется, но изменяется, изменяя то, на что ссылается переменная. Изменяемый тип может измениться таким образом, а также может измениться "на месте".

Здесь есть разница.

x = something # immutable type
print x
func(x)
print x # prints the same thing

x = something # mutable type
print x
func(x)
print x # might print something different

x = something # immutable type
y = x
print x
# some statement that operates on y
print x # prints the same thing

x = something # mutable type
y = x
print x
# some statement that operates on y
print x # might print something different

Конкретные примеры

x = 'foo'
y = x
print x # foo
y += 'bar'
print x # foo

x = [1, 2, 3]
y = x
print x # [1, 2, 3]
y += [3, 2, 1]
print x # [1, 2, 3, 3, 2, 1]

def func(val):
    val += 'bar'

x = 'foo'
print x # foo
func(x)
print x # foo

def func(val):
    val += [3, 2, 1]

x = [1, 2, 3]
print x # [1, 2, 3]
func(x)
print x # [1, 2, 3, 3, 2, 1]

Вы должны понимать, что Python представляет все свои данные как объекты. Некоторые из этих объектов, такие как списки и словари, являются изменяемыми, что означает, что вы можете изменять их содержимое без изменения их идентичности. Другие объекты, такие как целые числа, числа с плавающей запятой, строки и кортежи, являются объектами, которые нельзя изменить. Простой способ понять это, если взглянуть на идентификатор объекта.

Ниже вы видите строку, которая является неизменной. Вы не можете изменить его содержание. Это поднимет TypeError если вы попытаетесь изменить это. Кроме того, если мы назначаем новое содержимое, вместо изменяемого содержимого создается новый объект.

>>> s = "abc"
>>>id(s)
4702124
>>> s[0] 
'a'
>>> s[0] = "o"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>> s = "xyz"
>>>id(s)
4800100
>>> s += "uvw"
>>>id(s)
4800500

Вы можете сделать это со списком, и это не изменит идентичность объектов

>>> i = [1,2,3]
>>>id(i)
2146718700
>>> i[0] 
1
>>> i[0] = 7
>>> id(i)
2146718700

Чтобы узнать больше о модели данных Python, вы можете взглянуть на справочник по языку Python:

Общий неизменный тип:

  1. номера: int(), float(), complex()
  2. неизменяемые последовательности: str(), tuple(), frozenset(), bytes()

Обычный изменяемый тип (почти все остальное):

  1. изменяемые последовательности: list(), bytearray()
  2. установить тип: set()
  3. тип отображения: dict()
  4. классы, экземпляры классов
  5. и т.п.

Один из способов быстро проверить, является ли тип изменчивым или нет, это использовать id() встроенная функция.

Примеры, использующие на целое число,

>>> i = 1
>>> id(i)
***704
>>> i += 1
>>> i
2
>>> id(i)
***736 (different from ***704)

используя в списке,

>>> a = [1]
>>> id(a)
***416
>>> a.append(2)
>>> a
[1, 2]
>>> id(a)
***416 (same with the above id)

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

intс и floatс неизменны. Если я сделаю

a = 1
a += 5

Это указывает на имя a на 1 где-то в памяти на первой строке. На второй строке это выглядит так, что 1добавляет 5, получает 6, то указывает a при этом 6 в памяти - это не изменило 1 к 6 в любом случае. Та же логика применима к следующим примерам, использующим другие неизменяемые типы:

b = 'some string'
b += 'some other string'
c = ('some', 'tuple')
c += ('some', 'other', 'tuple')

Для изменяемых типов я могу сделать что-то, что фактически изменит значение, где оно хранится в памяти. С:

d = [1, 2, 3]

Я создал список мест 1, 2, а также 3 в памяти. Если я тогда сделаю

e = d

Я просто указываю e к тому жеlistd указывает на. Тогда я могу сделать:

e += [4, 5]

И список, который оба e а также d точки будут обновлены, чтобы также иметь места 4 а также 5 в памяти.

Если я вернусь к неизменному типу и сделаю это с tuple:

f = (1, 2, 3)
g = f
g += (4, 5)

затем f все еще только указывает на оригиналtuple - ты указал g в совершенно новомtuple,

Теперь, с вашим примером

class SortedKeyDict(dict):
    def __new__(cls, val):
        return dict.__new__(cls, val.clear())

Где вы проходите

d = (('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2))

(который является tuple из tuples) как valвы получаете ошибку, потому что tupleу меня нет .clear() метод - вам придется пройти dict(d) как val чтобы это работало, и в этом случае вы получите пустой SortedKeyDict в следствии.

Разница между изменяемым и неизменным объектом

Определения

Изменяемый объект: объект, который можно изменить после его создания.
Неизменяемый объект: объект, который нельзя изменить после его создания.

В python попытаются изменить значение неизменяемого объекта, который он даст новому объекту.

Изменчивые объекты

Вот список объектов в Python, которые имеют изменяемый тип:

  1. list
  2. Dictionary
  3. Set
  4. bytearray
  5. user defined classes

Неизменные объекты

Вот список объектов в Python, которые имеют неизменный тип:

  1. int
  2. float
  3. decimal
  4. complex
  5. bool
  6. string
  7. tuple
  8. range
  9. frozenset
  10. bytes

Некоторые неотвеченные вопросы

Вопросы: Является ли строка неизменным типом?
Ответ: да, но вы можете объяснить это:Доказательство 1:

a = "Hello"
a +=" World"
print a

Выход

"Hello World"

В приведенном выше примере строка, созданная как "Hello", в конце концов изменилась на "Hello World". Это означает, что строка имеет изменяемый тип. Но это не мы можем проверить его идентичность и проверить, имеет ли он изменчивый тип или нет.

a = "Hello"
identity_a = id(a)
a += " World"
new_identity_a = id(a)
if identity_a != new_identity_a:
    print "String is Immutable"

Выход

String is Immutable

Доказательство 2:

a = "Hello World"
a[0] = "M"

Выход

TypeError 'str' object does not support item assignment

Вопросы: Является ли Tuple неизменным типом?
Ответ: да, этодоказательство 1:

tuple_a = (1,)
tuple_a[0] = (2,)
print a

Выход

'tuple' object does not support item assignment

Если вы переходите на Python с другого языка (кроме того, который очень похож на Python, например, Ruby), и настаиваете на том, чтобы понимать его с точки зрения этого другого языка, то здесь люди обычно путаются:

>>> a = 1
>>> a = 2 # I thought int was immutable, but I just changed it?!

В Python присваивание не является мутацией в Python.

В C++, если вы пишете a = 2 звонишь a.operator=(2), который будет мутировать объект, хранящийся в a, (И если в a, Это ошибка.)

В Python a = 2 ничего не делает с тем, что было сохранено в a; это просто означает, что 2 теперь хранится в a вместо. (И если в a, Все в порядке.)


В конечном счете, это является частью еще более глубокого различия.

Переменная в языке, подобном C++, является типизированным местом в памяти. Если a является int это означает, что это где-то 4 байта, которые, как знает компилятор, должны интерпретироваться как int, Итак, когда вы делаете a = 2, это меняет то, что хранится в этих 4 байтах памяти от 0, 0, 0, 1 в 0, 0, 0, 2, Если где-то есть другая переменная типа int, она имеет свои 4 байта.

Переменная в языке, подобном Python, - это имя объекта, у которого есть собственная жизнь. Там есть объект для числа 1 и еще один объект для числа 2, А также a не 4 байта памяти, которые представлены в виде int это просто имя, которое указывает на 1 объект. Это не имеет смысла для a = 2 превратить число 1 в число 2 (что дало бы любому программисту на Python слишком много возможностей для изменения фундаментальной работы вселенной); вместо этого он просто делает a забудь 1 объект и указать на 2 объект вместо


Итак, если назначение не является мутацией, что такое мутация?

  • Вызов метода, который задокументирован для мутации, например a.append(b), (Обратите внимание, что эти методы почти всегда возвращают None). Неизменяемые типы не имеют таких методов, как обычно бывает у изменяемых типов.
  • Присвоение части объекта, например a.spam = b или же a[0] = b, Неизменяемые типы не позволяют присваивать атрибуты или элементы, изменяемые типы обычно допускают одно или другое.
  • Иногда, используя расширенное назначение, как a += bиногда нет. Изменчивые типы обычно изменяют значение; неизменные типы никогда не делают, и вместо этого дают вам копию (они вычисляют a + b, затем присвойте результат a).

Но если присваивание не является мутацией, как присваивается часть мутации объекта? Вот где это становится сложно. a[0] = b не мутирует a[0] (опять же, в отличие от C++), но он мутирует a (в отличие от C++, кроме косвенного).

Все это - то, почему, вероятно, лучше не пытаться интерпретировать семантику Python в терминах языка, к которому вы привыкли, а вместо этого изучать семантику Python на их собственных терминах.

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

Пользовательские типы (то есть классы), как правило, являются изменяемыми. Есть некоторые исключения, такие как простые подклассы неизменяемого типа. Другие неизменные типы включают некоторые встроенные типы, такие как int, float, tuple а также str, а также некоторые классы Python, реализованные в C.

Общее объяснение из главы "Модель данных" в Справочнике по языку Python ":

Стоимость некоторых объектов может измениться. Объекты, чье значение может измениться, называются изменяемыми; объекты, чье значение не подлежит изменению после их создания, называются неизменяемыми.

(Значение неизменяемого объекта-контейнера, содержащего ссылку на изменяемый объект, может изменяться при изменении значения последнего; однако контейнер по-прежнему считается неизменным, поскольку коллекция объектов, которую он содержит, не может быть изменена. Таким образом, неизменность не является строго то же самое, что иметь неизменяемое значение, оно более тонкое.)

Изменчивость объекта определяется его типом; например, числа, строки и кортежи неизменны, а словари и списки изменчивы.

Изменяемый объект должен иметь, по крайней мере, метод, способный изменять объект. Например, list объект имеет append метод, который на самом деле будет мутировать объект:

>>> a = [1,2,3]
>>> a.append('hello') # `a` has mutated but is still the same object
>>> a
[1, 2, 3, 'hello']

но класс float не имеет метода для изменения объекта с плавающей точкой. Ты можешь сделать:

>>> b = 5.0 
>>> b = b + 0.1
>>> b
5.1

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

Когда вы делаете b = b + 0.1 = операнд связывает переменную с новым float, который создается с результатом 5 + 0.1,

Когда вы присваиваете переменную существующему объекту, изменяемому или нет, = операнд связывает переменную с этим объектом. И больше ничего не происходит

В любом случае, = просто сделайте привязку. Он не меняет и не создает объекты.

Когда вы делаете a = 1.0, = операнд не создает поплавок, а 1.0 часть линии. На самом деле, когда вы пишете 1.0 это сокращение для float(1.0) вызов конструктора, возвращающий объект с плавающей точкой. (Вот почему, если вы печатаете 1.0 и нажмите Enter, вы получите "эхо" 1.0 напечатано ниже; это возвращаемое значение функции конструктора, которую вы вызвали)

Сейчас если b это поплавок, и вы назначаете a = bобе переменные указывают на один и тот же объект, но на самом деле переменные не могут соединяться между собой, потому что объект неизменен, и если вы делаете b += 1, сейчас b указать на новый объект и a все еще указывает на старика и не может знать, что b указывает на.

но если c скажем так, list, и вы назначаете a = c, сейчас a а также c может "общаться", потому что list изменчив, и если вы делаете c.append('msg')потом просто проверяю a Вы получите сообщение.

(Кстати, каждый объект имеет уникальный идентификационный номер, с которым вы можете получить id(x), Таким образом, вы можете проверить, является ли объект тем же или нет, проверяя, не изменился ли его уникальный идентификатор.)

Класс является неизменным, если каждый объект этого класса имеет фиксированное значение при создании экземпляра, которое не может быть ПОСЛЕДОВАТЕЛЬНО изменено

Другими словами, измените все значение этой переменной (name) или оставь это в покое.

Пример:

my_string = "Hello world" 
my_string[0] = "h"
print my_string 

вы ожидали, что это сработает, и выведите hello world, но это приведет к следующей ошибке:

Traceback (most recent call last):
File "test.py", line 4, in <module>
my_string[0] = "h"
TypeError: 'str' object does not support item assignment

Переводчик говорит: я не могу изменить первый символ этой строки

вам придется изменить весь string для того, чтобы заставить это работать:

my_string = "Hello World" 
my_string = "hello world"
print my_string #hello world

проверьте эту таблицу:

введите описание изображения здесь

источник

Цель этого ответа - создать единое место, где можно найти все хорошие идеи о том, как определить, имеете ли вы дело с мутирующим / не мутирующим (неизменяемым / изменяемым) и где это возможно, что с этим делать? Есть моменты, когда мутация нежелательна, и поведение Python в этом отношении может показаться нелогичным для кодеров, входящих в нее с других языков.

Согласно полезному сообщению @mina-gabriel:

Анализируя вышеизложенное и объединяя w/ a post @arrakëën:

Что не может неожиданно измениться?

  • скаляры (типы переменных, хранящие одно значение) не изменяются неожиданно
    • числовые примеры: int(), float(), complex()
  • Есть несколько "изменяемых последовательностей":
    • str (), tuple (), frozenset (), bytes ()

Что можешь?

  • список как объекты (списки, словари, наборы, bytearray())
  • пост здесь также говорит о классах и экземплярах классов, но это может зависеть от того, от чего наследуется класс и / или как он построен.

под "неожиданно" я подразумеваю, что программисты из других языков могут не ожидать такого поведения (за исключением Ruby и, возможно, нескольких других "похожих на Python") языков.

Добавление к этому обсуждению:

Такое поведение является преимуществом, когда оно предотвращает случайное заполнение вашего кода множественными копиями больших объемов данных, потребляющих память. Но когда это нежелательно, как мы можем обойти это?

С помощью списков простое решение - создать новый, например:

list2 = список (list1)

с другими структурами... решение может быть сложнее. Одним из способов является циклическое перемещение элементов и добавление их в новую пустую структуру данных (того же типа).

функции могут изменять оригинал, когда вы передаете изменяемые структуры. Как сказать?

  • Есть несколько тестов для других комментариев в этой теме, но есть комментарии, указывающие, что эти тесты не являются полным доказательством
  • object.function() - это метод исходного объекта, но только некоторые из них видоизменяются. Если они ничего не возвращают, они, вероятно, делают. Можно было бы ожидать, что.append() будет видоизменяться без проверки его имени. .union() возвращает объединение set1.union(set2) и не мутирует. В случае сомнений функцию можно проверить на возвращаемое значение. Если return = None, он не видоизменяется.
  • sorted () может быть обходным решением в некоторых случаях. Поскольку он возвращает отсортированную версию оригинала, он может позволить вам сохранить немутатную копию, прежде чем вы начнете работать с оригиналом другими способами. Тем не менее, эта опция предполагает, что вы не заботитесь о порядке исходных элементов (если вы это делаете, вам нужно найти другой путь). В отличие от.sort() мутирует оригинал (как и следовало ожидать).

Нестандартные подходы (в случае полезности): Обнаружил это на github, опубликованном под лицензией MIT:

  • хранилище github под: tobgu с именем: pyrsistent
  • Что это такое: код постоянной структуры данных Python, написанный для использования вместо основных структур данных, когда мутация нежелательна

Для пользовательских классов @semicolon предлагает проверить, есть ли __hash__ функция, потому что изменяемые объекты, как правило, не должны иметь __hash__() функция.

Это все, что я накопил на эту тему на данный момент. Другие идеи, исправления и т. Д. Приветствуются. Благодарю.

Мне кажется, что вы боретесь с вопросом, что на самом деле означает изменчивое / неизменяемое. Итак, вот простое объяснение:

Сначала нам нужен фундамент, на котором можно основывать объяснение.

Поэтому думайте обо всем, что вы программируете как виртуальный объект, что-то, что сохраняется в памяти компьютера как последовательность двоичных чисел. (Однако не пытайтесь представить это слишком сложно.^^) Теперь на большинстве компьютерных языков вы не будете работать с этими двоичными числами напрямую, а скорее будете использовать интерпретацию двоичных чисел.

Например, вы не думаете о числах, таких как 0x110, 0xaf0278297319 или аналогичных, но вместо этого вы думаете о числах, таких как 6, или о строках, таких как "Hello, world". Тем не менее эти числа или строки являются интерпретацией двоичного числа в памяти компьютера. То же самое верно для любого значения переменной.

Вкратце: мы программируем не с фактическими значениями, а с интерпретациями реальных двоичных значений.

Теперь у нас есть интерпретации, которые не должны быть изменены ради логики и других "изящных вещей", в то время как есть интерпретации, которые вполне могут быть изменены. Например, подумайте о симуляции города, другими словами, о программе, в которой много виртуальных объектов, а некоторые из них - дома. Теперь можно ли изменить эти виртуальные объекты (дома) и можно ли считать их одними и теми же домами? Ну, конечно, они могут. Таким образом, они изменчивы: их можно изменять, не становясь "совершенно" другим объектом.

Теперь подумайте о целых числах: это также виртуальные объекты (последовательности двоичных чисел в памяти компьютера). Так что, если мы изменим один из них, например, увеличив значение шесть на одно, это все равно шесть? Ну, конечно, нет. Таким образом, любое целое число является неизменным.

Итак: если какое-либо изменение в виртуальном объекте означает, что оно фактически становится другим виртуальным объектом, то оно называется неизменным.

Заключительные замечания:

(1) Никогда не путайте свой реальный опыт изменчивости и неизменности с программированием на определенном языке:

Каждый язык программирования имеет свое собственное определение того, какие объекты могут быть приглушены, а какие нет.

Таким образом, хотя вы теперь можете понимать разницу в значении, вам все равно придется изучать фактическую реализацию для каждого языка программирования.... Действительно, может быть цель языка, где 6 может быть приглушен, чтобы стать 7. С другой стороны, это может быть довольно сумасшедший или интересный материал, например симуляции параллельных вселенных. ^^

(2) Это объяснение, конечно, не научное, оно призвано помочь вам понять разницу между изменчивым и неизменным.

Один способ думать о разнице:

Назначения неизменяемых объектов в python можно рассматривать как глубокие копии, тогда как назначения изменяемым объектам являются поверхностными.

Самый простой ответ:

Изменяемая переменная - это переменная, значение которой может измениться на месте, тогда как в неизменяемой переменной изменение значения не произойдет. Изменение неизменяемой переменной перестроит ту же переменную.

Пример:

>>>x = 5

Создаст значение 5, на которое ссылается х

х -> 5

>>>y = x

Это утверждение заставит вас сослаться на 5 из х

x -------------> 5 <----------- y

>>>x = x + y

Поскольку x является целым числом (неизменный тип), оно было перестроено.

В этом выражении выражение RHS приведет к значению 10, а когда оно будет присвоено LHS (x), x перестроится в 10. Итак, теперь

х --------->10

у ---------> 5

Каждый раз, когда мы меняем значение неизменяемой переменной, он в основном уничтожает предыдущий экземпляр и создает новый экземпляр класса переменной.

      var = 2 #Immutable data
print(id(var))
var += 4
print(id(var))

list_a = [1,2,3] #Mutable data
print(id(list_a))
list_a[0]= 4
print(id(list_a))

Выход:

      9789024
9789088
140010877705856
140010877705856

Примечание. Изменяемая переменная memory_location изменяется, когда мы меняем значение

Например, для неизменяемых объектов присваивание создает новую копию значений.

x=7
y=x
print(x,y)
x=10 # so for immutable objects this creates a new copy so that it doesnot 
#effect the value of y
print(x,y)

Для изменяемых объектов присвоение не создает другую копию значений. Например,

x=[1,2,3,4]
print(x)
y=x #for immutable objects assignment doesn't create new copy 
x[2]=5
print(x,y) # both x&y holds the same list

Мутабельный означает, что он может изменяться / видоизменяться . Неизменяемо противоположное.

Некоторые типы данных Python изменяемы, другие нет.

Давайте выясним, какие типы подходят для каждой категории, и посмотрим на несколько примеров.


Мутабельный

В Python есть различные изменяемые типы:

  • списки

  • диктовать

  • набор

Давайте посмотрим на следующий пример для lists.

      list = [1, 2, 3, 4, 5]

Если я сделаю следующее, чтобы изменить первый элемент

      list[0] = '!'
#['!', '2', '3', '4', '5']

Он отлично работает, так как списки изменяемы.

Если мы рассмотрим этот список, он был изменен, и присвоим ему переменную

      y = list

И если мы изменим элемент из списка, например

      list[0] = 'Hello'
#['Hello', '2', '3', '4', '5']

И если напечатать, это даст

      ['Hello', '2', '3', '4', '5']

Как оба list и y ссылаются на тот же список, и мы изменили список.


Неизменный

В некоторых языках программирования можно определить такую ​​константу, как следующая

      const a = 10

А если позвонить, выдаст ошибку

      a = 20

Однако в Python этого нет.

Однако в Python существуют различные неизменяемые типы:

  • Никто

  • bool

  • int

  • плавать

  • ул

  • кортеж

Давайте посмотрим на следующий пример для strings.

Взяв строку

      a = 'abcd'

Мы можем получить первый элемент с помощью

      a[0]
#'a'

Если кто-то пытается присвоить новое значение элементу в первой позиции

      a[0] = '!'

Выдаст ошибку

Объект 'str' не поддерживает назначение элементов

Когда кто-то говорит += строке, например

      a += 'e'
#'abcde'

Не выдаёт ошибку, потому что указывает a на другую строку.

Это будет то же самое, что и следующее

      a = a + 'f'

И не меняя струны.

Некоторые плюсы и минусы неизменности

• Объем памяти известен с самого начала. Это не потребует дополнительного места.

• Обычно это делает вещи более эффективными. Обнаружение, например, len() строки выполняется намного быстрее, поскольку она является частью строкового объекта.

Обновление: не обращайте внимания на мои ошибочные замечания в этом ответе. Все переменные передаются по ссылке. Я исправлю этот ответ позже. См. этот комментарий и комментарии под моим ответом: Неизменяемые и изменяемые типы.

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


Изменяемые типы против неизменяемых типов в Python

1. Резюме

  1. Mutable на простом английском языке означает изменчивый. Подумайте: «мутация».

  2. Неизменяемый означает неизменяемый .

  3. В Python нет понятия констант. Неизменяемый или изменяемый не означает постоянный или не постоянный соответственно. Скорее, это означает неизменяемую --> общую память (один базовый объект в памяти посредством динамического распределения памяти для заданного буквального значения, присвоенного переменным) и изменяемую --> неразделяемую память (несколько базовых объектов в памяти посредством динамического распределения памяти, для данного буквального значения, присвоенного переменным) соответственно. Подробнее об этом я расскажу ниже, так как это довольно нюансы.

    1. Как следствие, это также означает передачу по ссылке (изменяемую) или передачу по значению (неизменяемую), поскольку объекты, которые способны поддерживать свои собственные уникальные базовые объекты в памяти, могут передавать эти изменяемые фрагменты памяти по ссылке, чтобы их можно было изменять..
  4. Все в Python является объектом. Четные числа, целые числа, числа с плавающей запятой и т. д. являются объектами. Все переменные являются объектами.

  5. Типы изменяемых и неизменяемых объектов в Python перечислены ниже.

  6. Изменяемые типы передаются по ссылке и вызывают побочные эффекты .

    1. Если ты это сделаешьmy_dict3 = my_dict2 = my_dict1 = {}, затем измениmy_dict3, оно тоже меняетсяmy_dict2 и my_dict1. Это побочный эффект. Это связано с тем, что каждая переменная указывает на один и тот же базовый объект (объект памяти).

    2. Опять же, если вы назначаете изменяемые типы по цепочке, каждая изменяемая переменная указывает на один и тот же базовый объект в памяти, поскольку значение передается от одной переменной к другой по ссылке :

                my_dict1 = {"key": "value"}
      # Copy **by reference**, so all variables point to the same 
      # underlying object.
      my_dict2 = my_dict1
      my_dict3 = my_dict2
      

      Таким образом, все следующее :

                # Each of these is True because the underlying object is the same
      # blob of memory.
      print(my_dict3 is my_dict2)    # True
      print(my_dict2 is my_dict1)    # True
      print(my_dict3 is my_dict1)    # True
      # And each of these is True because all variables have the same value.
      print(my_dict3 == my_dict2)    # True
      print(my_dict2 == my_dict1)    # True
      print(my_dict3 == my_dict1)    # True
      
    3. Однако если вы независимо присваиваете одно и то же буквальное значение изменяемым типам, каждая изменяемая переменная указывает на свой собственный базовый объект в памяти, поскольку ожидается, что она сможет независимо изменять память, на которую указывает:

                # **Mutable type:** each variable has an **independent underlying 
      # object**, even though each of those underlying objects has the same
      # value.
      my_dict1 = {"key": "value"}
      my_dict2 = {"key": "value"}
      my_dict3 = {"key": "value"}
      # Therefore, each of these is False because the underlying objects 
      # differ.
      print(my_dict3 is my_dict2)    # False
      print(my_dict2 is my_dict1)    # False
      print(my_dict3 is my_dict1)    # False
      # But, each of these is True because all variables have the same value.
      print(my_dict3 == my_dict2)    # True
      print(my_dict2 == my_dict1)    # True
      print(my_dict3 == my_dict1)    # True
      
    4. Если вы передаете изменяемую переменную функции, и эта функция изменяет ее, модификация автоматически будет видна за пределами функции. Это побочный эффект :

                def modify_dict(my_dict):
          my_dict["new_key"] = "new_value"
      
      my_dict1 = {"key": "value"}
      modify_dict(my_dict1)
      print(my_dict1)  # prints: {"key": "value", "new_key": "new_value"}
      
    5. Чтобы заставить изменяемый тип передаваться по значению, а не по ссылке , вы можете вызвать метод.copy()метод, позволяющий принудительно создать копию базового объекта.

                my_dict1 = {"key": "value"}
      # Force-copy **by value**, so each variable has its own underlying 
      # object. The `.copy()` method makes an entirely new copy of the 
      # underlying object.
      my_dict2 = my_dict1.copy()
      my_dict3 = my_dict2.copy()
      # Therefore, each of these is False because the underlying objects 
      # differ.
      print(my_dict3 is my_dict2)    # False
      print(my_dict2 is my_dict1)    # False
      print(my_dict3 is my_dict1)    # False
      # But, each of these is True because all variables have the same value.
      print(my_dict3 == my_dict2)    # True
      print(my_dict2 == my_dict1)    # True
      print(my_dict3 == my_dict1)    # True
      
  7. Неизменяемые типы передаются путем копирования и не вызывают побочных эффектов .

    1. Если ты это сделаешьmy_int3 = my_int2 = my_int1 = 1, затем измениmy_int3, оно не меняетсяmy_int2илиmy_int1, потому что это будет побочный эффект. Не имеет побочных эффектов.

    2. Однако если вы присвоите одно и то же значение нескольким неизменяемым переменным, будь то путем цепного присваивания или независимого литерального присваивания , обе переменные будут равны (var1 == var2есть ) и они одинаковые (var1 is var2являетсяTrue), как показано здесь:

                ## **Immutable types:** each variable apparently has the **same 
      # underlying object**, but side effects are not allowed
      my_int1 = 7
      my_int2 = 7
      my_int3 = 7
      # Therefore, each of these is True because the underlying objects are 
      # the same.
      print(my_int3 is my_int2)      # True
      print(my_int2 is my_int1)      # True
      print(my_int3 is my_int1)      # True
      # And, each of these is also True because all variables have the 
      # same value.
      print(my_int3 == my_int2)      # True
      print(my_int2 == my_int1)      # True
      print(my_int3 == my_int1)      # True
      
      # Try the test again, this time like this
      my_int1 = 7
      my_int2 = my_int1
      my_int3 = my_int2
      # Same as above: same underlying object, so each of these is True
      print(my_int3 is my_int2)      # True
      print(my_int2 is my_int1)      # True
      print(my_int3 is my_int1)      # True
      # Same as above: same value, so each of these is True
      print(my_int3 == my_int2)      # True
      print(my_int2 == my_int1)      # True
      print(my_int3 == my_int1)      # True
      print()
      

      Это немного сбивает с толку, но неизменность и отсутствие побочных эффектов верны.

    3. Если вы передаете неизменяемую переменную функции, и эта функция изменяет ее, изменение не будет видно за пределами функции. Побочных эффектов нет. Скорее, если вы хотите увидеть изменение вне функции, вы должны вернуть измененную переменную из функции и переназначить ее вне функции.

                def modify_int(my_int):
          my_int += 1
          return my_int
      
      my_int1 = 7
      # reassign the returned value to obtain the change from inside the
      # function
      my_int1 = modify_int(my_int1)  
      print(my_int1)  # prints: 8
      
    4. Чтобы заставить неизменяемый тип передаваться по ссылке, а не по копии , вы можете просто обернуть неизменяемый тип внутри изменяемого типа, например списка, а затем передать изменяемую оболочку функции. Таким образом, он передается по ссылке, и побочный эффект изменения его содержимого автоматически виден за пределами функции:

                def modify_immutable_type_hack(var_list):
          var_list[0] += 1  # increment the number inside the first element
      
      my_int = 10
      my_int_list = [my_int]
      print(my_int_list[0])  # 10
      # Force an immutable type to act mutable by passing it inside a list,
      # which is a mutable type, into a function. This way, the "side effect"
      # of the change to the list being visible outside the function is still
      # seen. This is because the list gets passed **by reference** instead
      # of **by value.**
      modify_immutable_type_hack(my_int_list)
      print(my_int_list[0])  # 11
      print(my_int)          # 10
      

      Однако для одиночных чисел проще использовать метод возвращаемого значения, описанный выше:my_int1 = modify_int(my_int1).

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

  9. Python — непростой язык программирования. Таких нюансов масса. Это просто популярно, необычно и очень высокого уровня.

Весь тестовый код для вышеуказанного обучения приведен ниже.

2. Список изменяемых и неизменяемых объектов в Python

Вот список наиболее распространенных изменяемых и неизменяемых объектов в Python. Этот список можно получить,1) кропотливо поискав на официальной справочной странице Python «Встроенные типы» слова «mutable» и «immutable» или 2) запросив его у GitHub Copilot (или BingAI или ChatGPT). Я сделал и то, и другое. Последнее, конечно, намного быстрее, но требует проверки. Я проверил и обновил приведенный ниже список на основе собственных выводов и добавил все цитаты, в основном из официальной документации.

Изменяемые объекты:

  • list — «Списки представляют собой изменяемые последовательности»
  • - "Тип набора является изменяемым"
  • dict — «Объект сопоставления сопоставляет хешируемые значения с произвольными объектами. Сопоставления — это изменяемые объекты. В настоящее время существует только один стандартный тип сопоставления — словарь » .
  • байтаррей
    • «Поскольку объекты bytearray являются изменяемыми, они поддерживают операции с изменяемой последовательностью в дополнение к обычным операциям с байтами и байтовыми массивами»
    • «Объекты bytarray изменяемы и имеют эффективный механизм перераспределения»
    • «Объекты byterray являются изменяемым аналогом объектов bytes».

Неизменяемые объекты:

  • Все числовые типы. В официальной документации Python ничего не говорится об их неизменности, но каждый найденный мной источник и искусственный интеллект подтверждают их неизменяемость, как и мое личное тестирование ниже. С RealPython.com: «Числовые типы... неизменяемы».
    • интервал
    • плавать
    • сложный
    • логическое значение
  • str — «Строки представляют собой неизменяемые последовательности кодовых точек Юникода».
  • bytes — «Объекты Bytes представляют собой неизменяемые последовательности отдельных байтов».
  • кортеж — «Кортежи — это неизменяемые последовательности, обычно используемые для хранения коллекций разнородных данных»
  • setзамороженный набор - «Тип замороженного набора является неизменяемым и хешируемым»
  • range — «Тип диапазона представляет собой неизменяемую последовательность чисел и обычно используется для повторения определенного количества циклов for».

Остановитесь здесь, если хотите. Вышеперечисленное является самым важным.


3. Переназначение

В Python можно переназначать все переменные, независимо от того, были ли они ранее присвоены изменяемым или неизменяемым типам. Однако поведение переназначения различно для изменяемых и неизменяемых типов, и его нельзя рассматривать исключительно в традиционных терминах и понимании памяти, подобных C и C++. Python — это Python, а Python — это другое.

Исходя из C и C++, которые являются моими основными языками, концепция «изменяемости» в Python довольно запутанна. По этой и другим причинам я не считаю Python «простым» языком или языком «для начинающих». Это просто очень мощный язык «сверхвысокого уровня», вот и все. В нем масса очень тонких и запутанных моментов. C в этом отношении более прямолинеен и конкретен. (А C++ просто сумасшедший ).

В C и C++ моя мысленная модель каждой переменной состоит в том, что это фрагмент байтов в памяти. Когда вы это сделаете:

      int var = 0;  // statically allocate bytes for `var`, and mutate them into a 0
var = 7;      // mutate the bits in `var` into a 7 now instead of a 0

... вы меняете байты вvarкусок памяти из битов, хранящий0, к битам, хранящим7. Вы «мутировали» память, выделенную этой переменной, то есть фрагмент памяти, выделенный для нее. Тип переменной просто определяет «линзу» (представьте: просмотр через волшебную стеклянную линзу, которая преобразует биты в числа, буквы и т. д.) или алгоритм интерпретации , с помощью которого вы будете интерпретировать эти биты (например: , ,char, и т. д.).

Однако в Python это не та мысленная модель, которую следует иметь для переменных. В Python думайте о переменных как об объектах, содержащих указатели на другие объекты , где все является объектом , и каждый объект содержит бит, определяющий, является ли он изменяемым или неизменяемым , а изменяемые переменные передаются по ссылке , тогда как неизменяемые переменные передаются по значению . Также обратите внимание, что объект — это очень сложный динамически выделяемый экземпляр класса, который управляет своим собственным состоянием и фрагментом памяти , что-то вроде стандартного контейнера в C++.

Вы можете сразу увидеть, что типыint,bool,floatи т. д. — это все классы (объекты) в Python, а не просто тривиальные типы, как в C и C++, следующим образом:

      type(7)     # returns <class 'int'>
type(True)  # returns <class 'bool'>
type(1.0)   # returns <class 'float'>

Итак, в Python, когда вы это делаете:

      # dynamically allocate a pointer variable object named `var`, then
# dynamically allocate an integer object with a `0` in it, then point `var`
# to that int object which contains a `0`.
var = 0  # 0 is the value contained inside an immutable type `int` object

# dynamically allocate a new integer object with a `7` in it, then point `var`
# to that new, underlying int object which contains a `7`. Therefore,
# this simply re-assigns the variable `var` from pointing to the `0`
# object, to  pointing to the `7` object; the `int` object with a `0`
# in it is now "orphaned" and I suspect will be garbage collected, but that 
# level of understanding is beyond me.
var = 7

В случае изменяемых объектов переназначение изменяет их байты на месте, а не динамически создает новый объект и указывает на него переменную. Например:

      my_dict = {}  # dynamically allocate a pointer variable named `my_dict`,
              # dynamically allocate a dict object, then point `my_dict` to
              # that dict object
my_dict["some_key"] = "some_value"  # mutate the dict object by adding a
                                    # key-value pair

Детали того, как реализуется эта характеристика «неизменяемый» или «изменяемый», на самом деле не так важны для программиста Python, как для программиста низкоуровневых встроенных систем C и C++, такого как я. Скорее, это что-то вроде «руки-волны». Программист Python должен просто слепо принять это и двигаться дальше. Некоторые программисты C++ высокого уровня тоже поступают таким же образом. Это вещь мышления.

Итак, если вы знаете краткое изложение в разделе 1 выше и список изменяемых и неизменяемых объектов в Python в разделе 2 выше, у вас все хорошо.

4. Передача по ссылке и передача по копии и побочные эффекты

Однако хороший программист Python должен знать, будет ли конкретный тип переменной передаваться по ссылке или путем копирования при передаче функции. Это очень важное различие, поскольку передача по ссылке вызывает побочные эффекты , а это означает, что изменения переменной в одном месте приведут к изменениям той или иной переменной в других местах, например, внутри или снаружи функции. Это очень важная концепция в Python, и для меня это главная причина, почему важно различать изменяемые и неизменяемые типы. В остальном меня не особо волнует, как они работают под капотом.

Снова:

  1. Передача изменяемого типа в функцию вызывает побочные эффекты , а это означает, что изменения этой переменной внутри функции будут видны за ее пределами.
  2. Передача неизменяемого типа в функцию не вызывает побочных эффектов, а это означает, что изменения этой переменной внутри функции не будут видны за ее пределами.

5. против

Оператор проверяет равенство значений, хранящихся в переменной. Я думаю, что оператор используется для проверки того, ссылаются ли две переменные на один и тот же объект в памяти. Опять же, нюансы того, как Python делает это «под капотом», мне непонятны. Однако см. мое резюме в разделе 1, где есть множество нюансов по сравнению с==.

В официальной документации Python операторы и описаны в разделе «Сравнение идентичности» документации здесь: https://docs.python.org/3/reference/expressions.html#is-not :

Операторыisиis notпроверить идентичность объекта:x is yверно тогда и только тогда, когдаxиyявляются одним и тем же объектом. Идентичность объекта определяется с помощью функции.x is not yдает обратное значение истинности.

Функция говорит: https://docs.python.org/3/library/functions.html#id :

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

Итак, я интерпретирую это так, что это уникальный адрес динамической памяти или индекс в карте, который назначается каждому объекту при создании объекта. Когда ты это делаешьmy_int3 = my_int2 = my_int1 = 7,id()из всех четырех частей один и тот же:

      my_int3 = my_int2 = my_int1 = 7
print(id(my_int1))  # 140201501393328
print(id(my_int2))  # 140201501393328
print(id(my_int3))  # 140201501393328
print(id(7))        # 140201501393328

Таким образом, все они кажутся одним и тем же основным объектом или сгустком памяти.

6. Немного тестового кода

Вот мой тестовый код. Почти все поведение моего тестового кода было для меня непредсказуемым, поскольку Python сильно отличается от C и C++, и я не мог угадать результаты большинства этих тестов до их запуска и обучения. У меня почти все было не так.

mutable_vs_immutable_types.py из моегорепозитория eRCaGuy_hello_world.

Примечание. тестовый код слишком длинный для вставки в этот ответ, поскольку ограничение переполнения стека составляет 30 000 символов. Итак, смотрите по ссылке выше.

Пример запуска и вывода:

      eRCaGuy_hello_world$ python/mutable_vs_immutable_types.py 
var before modification: True
var is a bool
var after modification:  False

var before modification: 1.0
var is a float
var after modification:  2.0

var before modification: 7
var is an int
var after modification:  8

var before modification: some words
var is a str (string)
var after modification:  some words; some more words

var before modification: [7, 8, 9]
var is a list
var after modification:  [7, 8, 9, 1]

var before modification: {'key1': 'value1', 'key2': 'value2'}
var is a dict
var after modification:  {'key1': 'value1', 'key2': 'value2', 'new_key': 'new_value'}

var before modification: (7, 8, 9)
var is a tuple
var after modification:  (1, 2, 3)

is_mutable(my_bool, True)                                    -->  immutable
is_mutable(my_float, 1.0)                                    -->  immutable
is_mutable(my_int, 7)                                        -->  immutable
is_mutable(my_str, "some words")                             -->  immutable
is_mutable(my_list, [7, 8, 9])                               -->  mutable
is_mutable(my_dict, {"key1": "value1", "key2": "value2"})    -->  mutable
is_mutable(my_tuple, (7, 8, 9))                              -->  immutable

int is immutable
list is mutable

MUTABLE TYPES
False
False
False
True
True
True

IMMUTABLE TYPES
True
True
True
True
True
True

integer types again
True
True
True
True
True
True

True
True
True
True
True
True

False
False
False
True
True
True

How to update immutable vs mutable variables in a function:

7
8

[7, 8, 9]
[7, 8, 9, 1]

10
11
10

Рекомендации

  1. Почти все ссылки — это мои ссылки и тестовый код выше.
  2. Это моя собственная работа, но для любопытных вот вступительная беседа с GitHub Copilot, которая поможет мне начать:

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

Допустим, вы создали список

a = [1,2]

Если бы вы сказали:

b = a
b[1] = 3

Даже если вы переназначили значение для B, оно также переназначит значение для a. Это потому, что когда вы назначаете "b = a". Вы передаете "Ссылку" объекту, а не копию значения. Это не относится к строкам, числам с плавающей запятой и т. Д. Это делает изменяемым список, словари и подобные записи, но логические значения, значения с плавающей запятой и т. Д. Неизменяемыми.

В Python есть простой способ узнать:

Неизменный:

    >>> s='asd'
    >>> s is 'asd'
    True
    >>> s=None
    >>> s is None
    True
    >>> s=123
    >>> s is 123
    True

Mutable:

>>> s={}
>>> s is {}
False
>>> {} is {}
Flase
>>> s=[1,2]
>>> s is [1,2]
False
>>> s=(1,2)
>>> s is (1,2)
False

А также:

>>> s=abs
>>> s is abs
True

Поэтому я думаю, что встроенная функция также неизменна в Python.

Но я действительно не понимаю, как работает float:

>>> s=12.3
>>> s is 12.3
False
>>> 12.3 is 12.3
True
>>> s == 12.3
True
>>> id(12.3)
140241478380112
>>> id(s)
140241478380256
>>> s=12.3
>>> id(s)
140241478380112
>>> id(12.3)
140241478380256
>>> id(12.3)
140241478380256

Это так странно.

Другие вопросы по тегам