Как проверить «неизменяемость на любой глубине» в Python? - PullRequest
9 голосов
/ 26 ноября 2011

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

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

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

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

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

Спасибо!

Ответы [ 4 ]

5 голосов
/ 26 ноября 2011

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

Скорее всего, вы заинтересованы в хэшабильности, которая обычно зависит от неизменяемости. Контейнеры, которые можно хэшировать, будут рекурсивно хэшировать свое содержимое (т. Е. Кортежи и frozensets). Итак, ваш тест равен 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'
2 голосов
/ 26 ноября 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: не существует простого, элегантного, общего способа сделать это.

2 голосов
/ 26 ноября 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, то эта проверка не будет работать.

1 голос
/ 11 ноября 2013

Такой тест абсолютно имеет смысл!

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

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

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

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

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
...