Проверить на изменчивость в Python? - PullRequest
28 голосов
/ 07 декабря 2010

Считайте, что это код :

a = {...} # a is an dict with arbitrary contents
b = a.copy()
  1. Какую роль играет изменчивость в ключах и значениях диктов?
  2. Как я могу обеспечить, чтобы изменения ключей или значений одного из них не отражались в другом?
  3. Как это связано с хэшируемым ограничением клавиш dict?
  4. Есть ли различия в поведении между Python 2.x и Python 3.x?

Как проверить, является ли тип изменчивым в Python?

Ответы [ 6 ]

18 голосов
/ 07 декабря 2010

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

2) Не разделяя значения между двумя диктовками. Можно делиться ключами, потому что они должны быть неизменными. Копирование словаря в смысле модуля copy, безусловно, безопасно. Здесь также можно вызвать конструктор dict: b = dict(a). Вы также можете использовать неизменяемые значения.

3) Все встроенные неизменяемые типы могут быть хэшируемыми. Все встроенные изменяемые типы не являются хэшируемыми. Чтобы объект был хэшируемым, он должен иметь один и тот же хэш в течение всего времени жизни, даже если он мутировал.

4) Не то чтобы я знал; Я описываю 2.x.

Тип является изменяемым, если он не является неизменным. Тип является неизменным, если он является встроенным неизменяемым типом: str, int, long, bool, float, tuple и, возможно, парой других, которые я забыл. Пользовательские типы всегда изменяемы.

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

9 голосов
/ 07 декабря 2010

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

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

class example(object):
    def __init__(self, a):
        self.value = a
    def __eq__(self, rhs):
        return self.value == rhs.value
    def __hash__(self):
        return hash(self.value)

a = example(1)
d = {a: "first"}
a.data = 2
print d[example(1)]

Здесь example является не неизменным;мы модифицируем его с помощью a.data = 2.Тем не менее, мы используем его как ключ хэша без каких-либо проблем.Зачем?Свойство, которое мы меняем, не влияет на равенство: хеш не изменяется, и example(1) всегда равен example(1), игнорируя любые другие свойства.

Наиболее распространенное использование этого - кэширование и запоминание: кэширование свойства или его отсутствие не приводит к логическому изменению объекта и обычно не влияет на равенство.

(я остановлюсь здесь - пожалуйста, не задавайте пять вопросов одновременно).

4 голосов
/ 07 декабря 2010

Есть модули MutableSequence, MutableSet, MutableMapping в модуле коллекции .Который может использоваться для проверки изменчивости готовых типов.

issubclass(TYPE, (MutableSequence, MutableSet, MutableMapping))

Если вы хотите использовать это для пользовательских типов, тип должен быть либо унаследован от одного из них, либо зарегистрирован как виртуальный подкласс.

class x(MutableSequence):
    ...

или

class x:
    ...

abc.ABCMeta.register(MutableSequence,x)
3 голосов
/ 07 декабря 2010

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

Однако мы все взрослые. Было бы неразумно внедрять __hash__, если вы действительно этого не хотели. Грубо говоря, это сводится к тому, что если тип действительно может использоваться в качестве словарного ключа, то он предназначен для использования таким образом.

Если вы ищете что-то, что подобно диктату, но также неизменное, тогда namedtuple может быть вашим лучшим выбором из того, что есть в стандартной библиотеке. По общему признанию, это не очень хорошее приближение, но это начало.

2 голосов
/ 07 декабря 2010
  1. dict ключи должны быть хэшируемыми, что означает, что они имеют неизменное значение хэш .dict значения могут быть или не быть изменяемыми;однако, если они изменчивы, это повлияет на ваш второй вопрос.

  2. "Изменения в ключах" не будут отражены между двумя диктовками.Изменения неизменяемых значений, таких как строки, также не будут отражены.Изменения в изменяемых объектах, таких как определяемые пользователем классы, будут отражены, поскольку объект хранится по идентификатору (то есть по ссылке).

    class T(object):
      def __init__(self, v):
        self.v = v
    
    
    t1 = T(5)
    
    
    d1 = {'a': t1}
    d2 = d1.copy()
    
    
    d2['a'].v = 7
    d1['a'].v   # = 7
    
    
    d2['a'] = T(2)
    d2['a'].v   # = 2
    d1['a'].v   # = 7
    
    
    import copy
    d3 = copy.deepcopy(d2) # perform a "deep copy"
    d3['a'].v = 12
    d3['a'].v   # = 12
    d2['a'].v   # = 2
    
  3. Я думаю, что это объясняется первыми двумя ответами.

  4. Не знаю, что я знаю об этом.

некоторые дополнительные мысли :

Для понимания поведения ключей необходимо знать две основные вещи: ключи должны быть hashable (что означает, что они реализуют object.__hash__(self)), и они также должны быть«сопоставимый» (что означает, что они реализуют что-то вроде object.__cmp__(self)).Один важный вывод из документов: по умолчанию хеш-функции пользовательских объектов возвращают id().

Рассмотрим этот пример:

class K(object):
  def __init__(self, x, y):
     self.x = x
     self.y = y
  def __hash__(self):
     return self.x + self.y

k1 = K(1, 2)
d1 = {k1: 3}
d1[k1] # outputs 3
k1.x = 5
d1[k1] # KeyError!  The key's hash has changed!
k2 = K(2, 1)
d1[k2] # KeyError!  The key's hash is right, but the keys aren't equal.
k1.x = 1
d1[k1] # outputs 3

class NewK(object):
  def __init__(self, x, y):
     self.x = x
     self.y = y
  def __hash__(self):
     return self.x + self.y
  def __cmp__(self, other):
     return self.x - other.x

nk1 = NewK(3, 4)
nd1 = {nk1: 5}
nd1[nk1] # outputs 5
nk2 = NewK(3, 7)
nk1 == nk2 # True!
nd1[nk2] # KeyError! The keys' hashes differ.
hash(nk1) == hash(nk2) # False
nk2.y = 4
nd1[nk2] # outputs 5

# Where this can cause issues:
nd1.keys()[0].x = 5
nd1[nk1] # KeyError! nk1 is no longer in the dict!
id(nd1.keys()[0]) == id(nk1)  # Yikes. True?!
nd1.keys()[0].x = 3
nd1[nk1]  # outputs 5
id(nd1.keys()[0]) == id(nk1)  # True!

Значения гораздо проще понять, в dict хранятся ссылки на объекты.Читайте разделы на хэш.Такие вещи, как строки, являются неизменяемыми, если вы «измените» их, то указание, в котором вы их изменили, теперь ссылается на новый объект.Объекты, которые являются изменяемыми, могут быть «изменены на месте», поэтому значение обоих диктов будет меняться.

d1 = {1: 'a'}
d2 = d1.copy()
id(d1[1]) == id(d2[1]) # True
d2[1] = 'z'
id(d1[1]) == id(d2[1]) # False

# the examples in section 2 above have more examples of this.

В любом случае, вот основные пункты всего этого:

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

Я не думаю, что существует общий способ проверки любой из этих точек.Тесты на пригодность будут зависеть от вашего варианта использования.Например, может быть достаточно проверить, что объект выполняет или не реализует __hash__ и функции сравнения (__eq__ или __cmp__).Аналогично, вы можете каким-то образом «проверить» метод __setattr__ объекта, чтобы определить, является ли он изменяемым.

0 голосов
/ 07 декабря 2010

Dicts - неупорядоченные наборы пар ключ: значение. Ключи должны быть неизменяемыми и, следовательно, хэшируемыми. Чтобы определить, является ли объект хэшируемым, вы можете использовать функцию hash():

>>> hash(1)
1
>>> hash('a')
12416037344
>>> hash([1,2,3])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> hash((1,2,3))
2528502973977326415
>>> hash({1: 1})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'

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

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