В Python некоторый тип называется изменяемым, если можно изменить значение любого объекта этого типа (без изменения идентификации объекта).
Какие из следующих встроенных типов являются изменяемыми, а какие — неизменяемыми? И почему?
import sys; type(sys)
возвращает <class 'module'>
)_io.TextIOWrapper
)В официальной ссылке сказано следующее здесь:
В отличие от объектов-функций, объекты кода неизменяемы и не содержат ссылок (прямых или косвенных) на изменяемые объекты.
Я думаю, это подразумевает, что функции изменяемы, поскольку объекты функций могут содержать ссылки (прямо или косвенно) на изменяемые объекты. Также всегда можно изменить код объекта-функции, не затрагивая его идентификатор (см. пример здесь).
Пользовательские функции являются изменяемыми, им можно назначать атрибуты, например.
def foo():
pass
foo.bar = 1 # Completely legal
Методы — это просто функции, которые реализуют протокол дескриптора и прикреплены к классу, поэтому они становятся связанными методами при доступе через экземпляр класса, поэтому определяемые пользователем методы так же изменяемы, как и функции. Даже без этого функции могут содержать ссылки на изменяемые аргументы по умолчанию, например:
def foo(x, unsafe_list=[]):
unsafe_list.append(x)
сохраняет аргумент unsafe_list
по умолчанию в объекте функции и изменяет его каждый раз при вызове функции.
Встроенные функции, реализованные на C, как правило, не изменяемы просто потому, что у них нет __dict__
для хранения произвольных атрибутов, если они не согласны явно, и не имеют одинаковой обработки аргументов по умолчанию, но они могут выбирать произвольные атрибуты. (functools.partial
делает это специально для того, чтобы гарантировать, что она продолжает работать как пользовательская функция, поэтому обертывание пользовательской функции с помощью functools.partial
не нарушает эту функциональность).
По той же причине модули также изменяемы; все они имеют словарь (который содержит все определенные в них переменные, функции, классы и т. д.), который вы можете изменить, просто назначив атрибуты импортированного модуля.
Файлоподобные объекты изменяемы по определению; у них есть состояние (указатель файла), которое изменяется просто при их чтении, а для тех, которые поддерживают seek
, вы можете вручную настроить этот указатель файла, а не просто неявно перемещать его через read
, итерацию и т. д.
Однако объект method
, возвращаемый function.__get__
(связанный метод), является неизменяемым. (Я думаю, вы не можете добавлять атрибуты, а атрибуты __self__
и __func__
доступны только для чтения.).
Осталась одна вещь, которая до сих пор меня смущает: глоссарий Python говорит, что «Изменяемые объекты могут менять свое значение, но сохранять свой идентификатор()». Я думаю, что в случае таких встроенных типов, как list
или bytearray
, мы можем заменить термин «значение» в определении глоссария на repr(object)
. Но как нам интерпретировать термин «значение» в случае объекта функции/модуля/файла? Если мы интерпретируем этот термин как repr(object)
, то эти три объекта окажутся неизменяемыми объектами (например, присвоение атрибутов функции obj не меняет ее repr
).
@Rodvi: repr
здесь совершенно не имеет значения. У некоторых вещей нет полезных/полных repr
, из этого невозможно получить никакой полезной информации об изменчивости. «значение» означает любое наблюдаемое состояние объекта. vars(function_or_module_name)
получит базовый dict
, хранящий (изменяемые) атрибуты. fileobj.tell()
будет сообщать о новых значениях при расширении, и простая итерация файлового объекта будет каждый раз показывать новые значения, очевидно, что у него есть состояние, которое меняется.
@chepner: Что забавно, так это то, что привязанные методы на самом деле имеют возможность устанавливать произвольные атрибуты, но они, похоже, заблокировали ее, все еще ставя __dict__
, поэтому вы можете установить их странным образом, а затем получить к ним обычный доступ. Если вы создаете класс Foo
с методом bar
, то do b = Foo().bar
, b.x = 1
не получается, но vars(b)['x'] = 1
работает нормально, а потом b.x
возвращает 1
, поэтому они изменяемы, они просто блокируют обычные средства установки атрибутов.
@ShadowRanger Я даже не буду пытаться придумать обоснование такого дизайна :)
@chepner: Теперь, когда я думаю об этом, я подозреваю, что они заблокировали прямое присвоение, чтобы избежать путаницы с f = Foo()
, делающим f.bar.x = 1
, что «работает», но поскольку связанный метод эфемерен, он перестанет существовать вместе с новым присвоенный атрибут немедленно (поэтому чтение f.bar.x
не удастся). Разрешение присваивания через __dict__
по-прежнему небезопасно (vars(f.bar)['x'] = 1
также будет нарушено), но они оставили это как вариант, поскольку вы вряд ли сделаете это случайно, в отличие от f.bar.x = 1
, и в редких случаях это может быть полезно.
«это подразумевает, что функции изменяемы, потому что объекты функций могут содержать ссылки (прямо или косвенно) на изменяемые объекты» - функции изменяемы, потому что функции изменяемы, вы можете, например, добавьте к ним произвольные атрибуты (обычные при мемоизации, например: stackoverflow.com/a/17268784/3001761 ). Пользовательские классы по определению не являются встроенными типами, и вы, безусловно, можете определить свои собственные неизменяемые вещи, если это необходимо ( stackoverflow.com/q/4828080/3001761), поэтому являются ли их экземпляры или нет, зависит от автора.