Хотя мне это никогда не было нужно, меня просто поразило, что создание неизменяемого объекта в Python может быть немного сложнее. Вы не можете просто переопределить __setattr__
, потому что тогда вы не можете даже установить атрибуты в __init__
. Подклассы кортежа - это трюк, который работает:
class Immutable(tuple):
def __new__(cls, a, b):
return tuple.__new__(cls, (a, b))
@property
def a(self):
return self[0]
@property
def b(self):
return self[1]
def __str__(self):
return "<Immutable {0}, {1}>".format(self.a, self.b)
def __setattr__(self, *ignored):
raise NotImplementedError
def __delattr__(self, *ignored):
raise NotImplementedError
Но тогда у вас есть доступ к a
и b
переменным через self[0]
и self[1]
, что раздражает.
Возможно ли это в Pure Python? Если нет, то как бы я сделал это с расширением C?
(Ответы, которые работают только в Python 3, являются приемлемыми).
Обновить:
Так подклассы кортежа является способом сделать это в чистом Python, который работает хорошо для дополнительной возможности доступа к данным , за исключением элементов [0]
, и [1]
т.д. Таким образом, чтобы закончить этот вопрос все , что не хватает на МЕТОДИЧЕСКОМ сделать это «правильно» в С, Я подозреваю, что было бы довольно просто, просто не реализовав ничего geititem
или setattribute
и т. Д. Но вместо того, чтобы делать это самостоятельно, я предлагаю вознаграждение за это, потому что я ленивый. :)
источник
.a
и.b
? Это то, что свойства, кажется, существуют в конце концов.NotImplemented
подразумевается только как возвращаемое значение для богатых сравнений. Возвращаемое значение в__setatt__()
любом случае довольно бессмысленно, так как обычно вы его вообще не увидите. Код вродеimmutable.x = 42
молча ничего не сделает. Вы должны поднятьTypeError
вместо.NotImplementedError
, ноTypeError
это то, что поднимает кортеж, если вы пытаетесь изменить его.Ответы:
Еще одно решение, о котором я только что подумал: самый простой способ получить то же поведение, что и ваш исходный код, это
Это не решает проблему доступа к атрибутам через
[0]
и т. Д., Но, по крайней мере, она значительно короче и обеспечивает дополнительное преимущество совместимости сpickle
иcopy
.namedtuple
создает тип, подобный тому, что я описал в этом ответе , т.е. происходит отtuple
и использует__slots__
. Он доступен в Python 2.6 или выше.источник
verbose
параметра дляnamedtuple
кода легко генерируется)) заключается в том, что единый интерфейс / реализацияnamedtuple
предпочтительнее десятков очень немного отличающихся рукописных интерфейсов / реализаций, которые сделать почти то же самое.namedtuple
s копироваться по значению при прохождении через функции?Самый простой способ сделать это с помощью
__slots__
:Экземпляры
A
являются неизменяемыми, поскольку вы не можете устанавливать для них какие-либо атрибуты.Если вы хотите, чтобы экземпляры класса содержали данные, вы можете комбинировать это с производным от
tuple
:Изменить : Если вы хотите избавиться от индексации, вы можете переопределить
__getitem__()
:Обратите внимание, что вы не можете использовать
operator.itemgetter
для свойств в этом случае, так как это будет полагатьсяPoint.__getitem__()
вместоtuple.__getitem__()
. Более того, это не помешает использованиюtuple.__getitem__(p, 0)
, но я с трудом представляю, как это должно представлять проблему.Я не думаю, что «правильным» способом создания неизменяемого объекта является написание C-расширения. Python, как правило, полагается на то, что разработчики библиотек и пользователи библиотек согласны на взрослость , и вместо того, чтобы действительно применять интерфейс, интерфейс должен быть четко указан в документации. Вот почему я не рассматриваю возможность обойти переопределенное
__setattr__()
, называяobject.__setattr__()
проблему. Если кто-то делает это, это на свой страх и риск.источник
tuple
здесь__slots__ = ()
, а не__slots__ = []
? (Просто__slots__
не изменится ли правильно? Его цель - определить, какие атрибуты можно установить. Так что неtuple
кажется ли это естественным выбором в таком случае?__slots__
я не могу установить какие-либо атрибуты. И если у меня есть,__slots__ = ('a', 'b')
то атрибуты a и b все еще изменчивы.__setattr__
поэтому оно лучше моего. +1 :)Вы можете использовать Cython для создания типа расширения для Python:
Работает как на Python 2.x, так и на 3.
тесты
Если вы не возражаете против поддержки индексирования, то
collections.namedtuple
предложение @Sven Marnach предпочтительнее:источник
namedtuple
(или, точнее, типа, возвращаемого функциейnamedtuple()
) являются неизменяемыми. Определенно.namedtuple
проходит все тесты (кроме поддержки индексации). Какое требование я пропустил?__weakref__
в Python?Другая идея - полностью запретить
__setattr__
и использоватьobject.__setattr__
в конструкторе:Конечно, вы можете использовать
object.__setattr__(p, "x", 3)
для измененияPoint
экземпляраp
, но ваша первоначальная реализация страдает от той же проблемы (попробуйтеtuple.__setattr__(i, "x", 42)
наImmutable
экземпляре).Вы можете применить тот же трюк в своей первоначальной реализации: избавиться от него
__getitem__()
и использовать егоtuple.__getitem__()
в функциях свойств.источник
__setattr__
, потому что дело не в том, чтобы быть надежным. Смысл в том, чтобы прояснить, что это не должно быть изменено и чтобы предотвратить изменение по ошибке.Вы можете создать
@immutable
декоратор, который либо переопределит,__setattr__
и заменит__slots__
пустой список, а затем украсит__init__
метод им.Редактировать: как отмечено в OP, изменение
__slots__
атрибута только предотвращает создание новых атрибутов , но не их изменение.Edit2: вот реализация:
Edit3: Использование
__slots__
ломает этот код, потому что если останавливает создание объекта__dict__
. Я ищу альтернативу.Edit4: ну вот и все. Это хакерский, но работает как упражнение :-)
источник
object.__setattr__()
ломает это stackoverflow.com/questions/4828080/…Использование замороженного класса данных
Для Python 3.7+ вы можете использовать класс данных с
frozen=True
опцией , которая является очень питонным и поддерживаемым способом сделать то, что вы хотите.Это будет выглядеть примерно так:
Так как подсказка типа требуется для полей классов данных, я использовал Any из
typing
модуля .Причины НЕ использовать Namedtuple
До Python 3.7 часто встречались именованные кортежи, используемые как неизменяемые объекты. Это может быть сложно во многих отношениях, один из них заключается в том, что
__eq__
метод между именованными кортами не учитывает классы объектов. Например:Как видите, даже если типы
obj1
иobj2
отличаются, даже если имена их полей различны,obj1 == obj2
все равно выдаетTrue
. Это потому, что__eq__
используемый метод является кортежем, который сравнивает только значения полей с учетом их позиций. Это может быть огромным источником ошибок, особенно если вы создаете подклассы этих классов.источник
Я не думаю, что это вполне возможно, за исключением использования кортежа или именованного кортежа. Независимо от того, что, если вы переопределите,
__setattr__()
пользователь всегда может обойти его, позвонивobject.__setattr__()
напрямую. Любое решение, от которого зависит,__setattr__
гарантированно не будет работать.Ниже приведено описание ближайшего, которое вы можете получить без использования какого-либо кортежа:
но это сломается, если вы попытаетесь достаточно сильно:
но использование Свеном
namedtuple
является действительно неизменным.Обновить
Так как вопрос был обновлен, чтобы спросить, как сделать это правильно в C, вот мой ответ о том, как сделать это правильно в Cython:
Первый
immutable.pyx
:и
setup.py
для его компиляции (используя командуsetup.py build_ext --inplace
:Тогда, чтобы попробовать это:
источник
Я создал неизменяемые классы, переопределив
__setattr__
и разрешив набор, если вызывающий объект__init__
:Этого еще не достаточно, так как это позволяет кому-либо
___init__
менять объект, но вы понимаете.источник
object.__setattr__()
ломает это stackoverflow.com/questions/4828080/…__init__
не очень удовлетворительна.В дополнение к отличным другим ответам я хотел бы добавить метод для Python 3.4 (или, возможно, 3.3). Этот ответ основан на нескольких предыдущих ответах на этот вопрос.
В Python 3.4 вы можете использовать свойства без установщиков для создания членов класса, которые нельзя изменить. (В более ранних версиях присваивание свойств без установщика было возможно.)
Вы можете использовать это так:
который напечатает
"constant"
Но вызов
instance.a=10
вызовет:Объяснение: свойства без сеттеров - очень недавняя особенность Python 3.4 (и я думаю, 3.3). Если вы попытаетесь присвоить такое свойство, возникнет ошибка. Используя слоты, я ограничиваю переменные-члены
__A_a
(__a
).Проблема: Назначение по-
_A__a
прежнему возможно (instance._A__a=2
). Но если вы присваиваете частную переменную, это ваша собственная ошибка ...Этот ответ среди других, однако, препятствует использованию
__slots__
. Использование других способов предотвращения создания атрибутов может быть предпочтительным.источник
property
также доступен на Python 2 (посмотрите на код в самом вопросе). Это не создает неизменный объект, попробуйте тесты из моего ответа, например,instance.b = 1
создает новыйb
атрибут.A().b = "foo"
т.е. не разрешить установку новых атрибутов.Вот элегантное решение:
Наследуйте от этого класса, инициализируйте ваши поля в конструкторе, и все готово.
источник
Если вас интересуют объекты с поведением, то namedtuple - почти ваше решение.
Как описано в нижней части документации namedtuple , вы можете получить свой собственный класс из namedtuple; а затем вы можете добавить поведение, которое вы хотите.
Например (код взят непосредственно из документации ):
Это приведет к:
Этот подход работает как для Python 3, так и для Python 2.7 (также протестирован на IronPython).
Единственным недостатком является то, что дерево наследования немного странно; но это не то, с чем ты обычно играешь.
источник
class Point(typing.NamedTuple):
Классы, которые наследуются от следующего
Immutable
класса, являются неизменными, как и их экземпляры, после__init__
завершения выполнения их метода. Поскольку это чистый python, как уже отмечали другие, ничто не мешает кому-либо использовать мутирующие специальные методы из базыobject
иtype
, но этого достаточно , чтобы остановить любой из мутирует класс / экземпляр случайно.Он работает, угоняя процесс создания класса с метаклассом.
источник
Я нуждался в этом некоторое время назад и решил сделать для него пакет Python. Начальная версия сейчас на PyPI:
Использовать:
Полные документы здесь: https://github.com/theengineear/immutable
Надеюсь, что это поможет, он оборачивает именованный кортеж, как уже обсуждалось, но делает создание экземпляров намного проще
источник
Этот способ не перестает
object.__setattr__
работать, но я все еще нашел его полезным:вам может потребоваться переопределить больше вещей (например
__setitem__
) в зависимости от варианта использования.источник
getattr
чтобы я мог предоставить значение по умолчанию дляfrozen
. Это немного упростило ситуацию. stackoverflow.com/a/22545808/5987__new__
переопределять. Внутри__setattr__
просто замените условное наif name != '_frozen' and getattr(self, "_frozen", False)
freeze()
функцию. Затем объект будет "один раз заморозить". Наконец, беспокоиться оobject.__setattr__
глупости, потому что «мы все здесь взрослые».Начиная с Python 3.7, вы можете использовать
@dataclass
декоратор в своем классе, и он будет неизменным, как структура! Тем не менее, он может или не может добавить__hash__()
метод к вашему классу. Quote:Вот пример из документов, связанных выше:
источник
frozen
, то есть@dataclass(frozen=True)
, но это в основном блокирует использование__setattr__
и__delattr__
как в большинстве других ответов здесь. Он просто делает это способом, совместимым с другими параметрами классов данных.Вы можете переопределить setattr и все еще использовать init для установки переменной. Вы бы использовали суперкласс setattr . вот код
источник
pass
вместоraise NotImplementedError
Сторонний
attr
модуль обеспечивает эту функциональность .Изменить: Python 3.7 принял эту идею в stdlib с
@dataclass
.attr
в соответствии с документацией реализует замороженные классы путем переопределения__setattr__
и оказывает незначительное влияние на производительность при каждом создании экземпляра.Если вы привыкли использовать классы в качестве типов данных, это
attr
может быть особенно полезно, поскольку оно заботится о шаблоне для вас (но не использует магию). В частности, он пишет для вас девять методов dunder (__X__) (если вы не отключите ни один из них), включая repr, init, hash и все функции сравнения.attr
также предоставляет помощника для__slots__
.источник
Итак, я пишу соответствующие Python 3:
Я) с помощью декоратора класса данных и набора замороженных = True. мы можем создавать неизменные объекты в Python.
для этого нужно импортировать класс данных из классов данных lib и нужно установить frozen = True
ех.
из классов данных импорт данных
о / р:
Источник: https://realpython.com/python-data-classes/
источник
Альтернативный подход заключается в создании оболочки, которая делает экземпляр неизменным.
Это полезно в ситуациях, когда только некоторые экземпляры должны быть неизменяемыми (например, аргументы вызовов функций по умолчанию).
Может также использоваться на неизменных фабриках как:
Также защищает от
object.__setattr__
других трюков, но допускает их из-за динамической природы Python.источник
Я использовал ту же идею, что и Алекс: мета-класс и «маркер инициализации», но в сочетании с перезаписью __setattr__:
Примечание: я вызываю мета-класс напрямую, чтобы он работал как для Python 2.x, так и для 3.x.
Это работает и со слотами ...:
... и множественное наследование:
Обратите внимание, что изменяемые атрибуты остаются изменяемыми:
источник
Одна вещь, которая на самом деле здесь не включена, это полная неизменность ... не только родительский объект, но и все дочерние объекты. например, кортежи / фрозенсеты могут быть неизменными, но объекты, частью которых он является, могут не быть. Вот небольшая (неполная) версия, которая делает достойную работу по обеспечению неизменности:
источник
Вы можете просто переопределить setAttr в последнем утверждении init. Тогда вы можете построить, но не изменить. Очевидно, вы можете переопределить объект usint. setAttr, но на практике большинство языков имеют некоторую форму отражения, поэтому неизменность всегда является утечкой абстракции. Неизменность - это скорее предотвращение случайного нарушения клиентами договора на объект. Я использую:
=============================
Первоначальное предлагаемое решение было неверным, оно было обновлено на основе комментариев, используя решение отсюда
Интересное оригинальное решение неверно, поэтому оно включено внизу.
===============================
Вывод :
======================================
Оригинальная реализация:
В комментариях было правильно указано, что это на самом деле не работает, поскольку предотвращает создание более одного объекта, так как вы переопределяете метод класса setattr, что означает, что секунда не может быть создана как self.a = will Ошибка при второй инициализации.
источник
Ниже приведено базовое решение для следующего сценария:
__init__()
может быть написано доступ к атрибутам, как обычно.Идея состоит в том, чтобы переопределять
__setattr__
метод и заменять его реализацию при каждом изменении замороженного состояния объекта.Поэтому нам нужен метод (
_freeze
), который хранит эти две реализации и переключается между ними по запросу.Этот механизм может быть реализован внутри пользовательского класса или унаследован от специального
Freezer
класса, как показано ниже:источник
Просто как
dict
У меня есть библиотека с открытым исходным кодом, где я делаю вещи функционально , поэтому полезно перемещать данные в неизменяемом объекте. Однако я не хочу преобразовывать мой объект данных, чтобы клиент взаимодействовал с ними. Итак, я придумал это - он дает вам похожий на dict объект, который неизменен + некоторые вспомогательные методы.
Кредит Sven Marnach в своем ответе на базовой реализации ограничения обновления свойств и удаления.
Вспомогательные методы
Примеры
источник