Как проверить неизменность на любой глубине в Python?

Я определяю объект Python как «неизменный на любой глубине» iff

  1. он (номинально) неизменен; и
  2. если это объект «контейнер», то он содержит только объекты, которые «неизменны на любой глубине»;

Например, ((1, 2), (3, 4)) неизменен на любой глубине, а ((1, 2), [3, 4]) - нет (даже если последний, в силу того, что он является кортежем, "номинально" неизменен).

Есть ли разумный способ проверить, является ли объект Python «неизменяемым на любой глубине»?

Относительно легко проверить первое условие (например, используя класс collections.Hashable и пренебрегая возможностью неправильно реализованного метода __hash__), но второе условие труднее проверить из-за неоднородности "контейнерных" объектов и средства перебора их "содержимого" ...

Спасибо!


person kjo    schedule 26.11.2011    source источник
comment
Я считаю, что такая проверка вообще невозможна и не очень полезна. У вас есть какие-нибудь варианты использования?   -  person    schedule 26.11.2011
comment
Почему хэшируемость означает неизменность?   -  person Gabe    schedule 26.11.2011
comment
stackoverflow.com/questions/2671376/hashable-immutable   -  person joaquin    schedule 26.11.2011
comment
Более того, в идеале хешируемый объект должен быть неизменяемым, но на самом деле это гарантируют только встроенные типы.   -  person Gabe    schedule 26.11.2011


Ответы (4)


Не существует общих тестов на неизменяемость. Объект является неизменным, только если ни один из его методов не может изменять базовые данные.

Скорее всего, вас интересует хэшируемость, которая обычно зависит от неизменности. Контейнеры, которые можно хешировать, будут рекурсивно хешировать свое содержимое (то есть кортежи и замороженные наборы). Итак, ваш тест сводится к запуску hash(obj), и если он завершится успешно, то его можно будет глубоко хэшировать.

IOW, ваш код уже использовал лучший доступный тест:

>>> a = ((1, 2), (3, 4))
>>> b = ((1, 2), [3, 4])
>>> hash(a)
5879964472677921951
>>> hash(b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
person Raymond Hettinger    schedule 26.11.2011

Я полагаю, вы ищете что-то вроде этого:

def deeply_hashable(obj):
    try:
        hash(obj)
    except TypeError:
        return False
    try:
        iter(obj)
    except TypeError:
        return True
    return all(deeply_hashable(o) for o in obj)

Одна очевидная проблема здесь заключается в том, что итерация dict выполняет итерацию по его ключам, которые всегда неизменяемы, а не по его значениям, что вас интересует. Нет простого способа обойти это, кроме, конечно, специального dict корпуса. - что не помогает с другими классами, которые могут вести себя аналогичным образом, но не являются производными от dict В конце я согласен с delnan: нет простого, элегантного и общего способа сделать это.

person kindall    schedule 26.11.2011

Я не уверен, что именно вы ищете. Но используя данные вашего примера:

>>> a = ((1, 2), (3, 4))
>>> b = ((1, 2), [3, 4])
>>> isinstance(a, collections.Hashable)
True
>>> isinstance(b, collections.Hashable)
True

Следовательно, действительно, использование collections.Hashable - не лучший вариант. Тем не мение,

>>> hash(a)
5879964472677921951
>>> hash(b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

Итак, по крайней мере, для данных примера использования hash достаточно, чтобы проверить, является ли объект хешируемым. Конечно, как вы уже указали в своем вопросе, если __hash__ неправильно реализован для подкласса, скажем, list, эта проверка не сработает.

person jcollado    schedule 26.11.2011

Совершенно разумно пройти такой тест!

Рассмотрим время 'deepcopy () - ing' (или ручного clone () - ing) объекта по сравнению с простым присвоением ссылки!

Представьте, что двум объектам должен принадлежать один и тот же объект, но полагайтесь на то, что он не изменяется (хороший пример - dict-keys).

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

Я бы подумал о том, чтобы рекурсивно проверить что-то вроде

def check(Candidate):
    if isinstance(Candidate, (str, int, long)):
        return True
    elif isinstance(Candidate, tuple):
        return not any(not check(x) for x in Candidate)
    else:
        return False
person Frank-Rene Schäfer    schedule 11.11.2013
comment
Полностью согласен с необходимостью проверки неизменяемости. И мне нравится твой рекурсивный тест. Но работает только с ограниченным количеством встроенных типов. Я думаю, можно ли расширить его для определенных пользователем классов ... т.е. проверить, все ли методы не изменяют объект. Но как это проверить? Любая идея? - person V.K.; 07.08.2014