Неизменяемые и изменчивые типы - PullRequest
168 голосов
/ 08 ноября 2011

Я не совсем понимаю, что такое неизменный тип. Я знаю, что объект 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__, и он не пометил ошибки.

Ответы [ 16 ]

219 голосов
/ 09 ноября 2011

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

x = 5.0
x += 7.0
print x # 12.0

Разве это не "mut" x?

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

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]
176 голосов
/ 09 ноября 2011

Вы должны понимать, что 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:

100 голосов
/ 18 мая 2014

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

  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)
33 голосов
/ 08 ноября 2011

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

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 на таких же list d точек на. Я могу тогда сделать:

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 s не имеют .clear() метода - вам нужно будет передать dict(d) как val для его работы, в этом случае вы получите пустой SortedKeyDict в результате.

19 голосов
/ 13 апреля 2015

Если вы переходите на 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 нет ни одного объекта, это нормально.)


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

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

Переменная в языке, подобном Python, - это имя объекта, который имеет собственную жизнь.Есть объект с номером 1 и еще один объект с номером 2a - это не 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 на их собственных терминах.

18 голосов
/ 08 ноября 2011

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

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

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

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

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

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

11 голосов
/ 13 ноября 2017

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

Определения

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

В 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
11 голосов
/ 22 января 2013

Изменяемый объект должен иметь хотя бы метод, способный изменять объект. Например, у объекта 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, операнд = связывает переменную с новым числом с плавающей точкой, которое создается с результатом 5 + 0.1.

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

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

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

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

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

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

5 голосов
/ 06 апреля 2016

Класс является неизменным , если каждый объект этого класса имеет фиксированное значение при создании экземпляра, которое нельзя ПОСЛЕДУЮЩИЙ изменить

Другими словами, измените все значение этой переменной (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

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

enter image description here

источник

4 голосов
/ 13 февраля 2017

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

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

Анализ вышеизложенного и объединение с постом @ arrakëën:

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

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

Что может?

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

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

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

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

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

list2 = list (list1)

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

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...