Являются ли эти встроенные типы изменяемыми или нет?

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

Какие из следующих встроенных типов являются изменяемыми, а какие — неизменяемыми? И почему?

  • функция (в частности, метод)
  • модуль (здесь я имею в виду тип импортируемого модуля, например команда import sys; type(sys) возвращает <class 'module'>)
  • тип файла (например, _io.TextIOWrapper)

В официальной ссылке сказано следующее здесь:

В отличие от объектов-функций, объекты кода неизменяемы и не содержат ссылок (прямых или косвенных) на изменяемые объекты.

Я думаю, это подразумевает, что функции изменяемы, поскольку объекты функций могут содержать ссылки (прямо или косвенно) на изменяемые объекты. Также всегда можно изменить код объекта-функции, не затрагивая его идентификатор (см. пример здесь).

«это подразумевает, что функции изменяемы, потому что объекты функций могут содержать ссылки (прямо или косвенно) на изменяемые объекты» - функции изменяемы, потому что функции изменяемы, вы можете, например, добавьте к ним произвольные атрибуты (обычные при мемоизации, например: stackoverflow.com/a/17268784/3001761 ). Пользовательские классы по определению не являются встроенными типами, и вы, безусловно, можете определить свои собственные неизменяемые вещи, если это необходимо ( stackoverflow.com/q/4828080/3001761), поэтому являются ли их экземпляры или нет, зависит от автора.

jonrsharpe 22.06.2024 15:16
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
1
92
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Все, что вы перечислили, по крайней мере потенциально изменчиво.

Пользовательские функции являются изменяемыми, им можно назначать атрибуты, например.

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__ доступны только для чтения.).

chepner 22.06.2024 16:07

Осталась одна вещь, которая до сих пор меня смущает: глоссарий Python говорит, что «Изменяемые объекты могут менять свое значение, но сохранять свой идентификатор()». Я думаю, что в случае таких встроенных типов, как list или bytearray, мы можем заменить термин «значение» в определении глоссария на repr(object). Но как нам интерпретировать термин «значение» в случае объекта функции/модуля/файла? Если мы интерпретируем этот термин как repr(object), то эти три объекта окажутся неизменяемыми объектами (например, присвоение атрибутов функции obj не меняет ее repr).

Rodvi 22.06.2024 17:00

@Rodvi: repr здесь совершенно не имеет значения. У некоторых вещей нет полезных/полных repr, из этого невозможно получить никакой полезной информации об изменчивости. «значение» означает любое наблюдаемое состояние объекта. vars(function_or_module_name) получит базовый dict, хранящий (изменяемые) атрибуты. fileobj.tell() будет сообщать о новых значениях при расширении, и простая итерация файлового объекта будет каждый раз показывать новые значения, очевидно, что у него есть состояние, которое меняется.

ShadowRanger 22.06.2024 17:30

@chepner: Что забавно, так это то, что привязанные методы на самом деле имеют возможность устанавливать произвольные атрибуты, но они, похоже, заблокировали ее, все еще ставя __dict__, поэтому вы можете установить их странным образом, а затем получить к ним обычный доступ. Если вы создаете класс Foo с методом bar, то do b = Foo().bar, b.x = 1 не получается, но vars(b)['x'] = 1 работает нормально, а потом b.x возвращает 1, поэтому они изменяемы, они просто блокируют обычные средства установки атрибутов.

ShadowRanger 22.06.2024 17:35

@ShadowRanger Я даже не буду пытаться придумать обоснование такого дизайна :)

chepner 22.06.2024 19:58

@chepner: Теперь, когда я думаю об этом, я подозреваю, что они заблокировали прямое присвоение, чтобы избежать путаницы с f = Foo(), делающим f.bar.x = 1, что «работает», но поскольку связанный метод эфемерен, он перестанет существовать вместе с новым присвоенный атрибут немедленно (поэтому чтение f.bar.x не удастся). Разрешение присваивания через __dict__ по-прежнему небезопасно (vars(f.bar)['x'] = 1 также будет нарушено), но они оставили это как вариант, поскольку вы вряд ли сделаете это случайно, в отличие от f.bar.x = 1, и в редких случаях это может быть полезно.

ShadowRanger 28.06.2024 13:12

Другие вопросы по теме