Изменчивый, неизменный... все является объектом! Python3

В компьютерном программировании самоанализ — это способность определять тип объекта во время выполнения. Это одна из сильных сторон Python. Почти все в Python является объектом со своими свойствами и методами.

id и тип

В программировании тип данных является важной концепцией. Переменные могут хранить данные разных типов, и разные типы могут выполнять разные действия. Функция type возвращает тип объекта. Например:

str = ""
number = 10
some_list = [1, 2, 3]
more_list = (1, 2, 3)
float = 8.4
type(str)
# Output: <class 'str'>
type(number)
# Output: <class 'int'>
type(some_list)
# Output: <class 'list'>
type(more_list)
# Output: <class 'tuple'>
type(float)
# Output: <class 'float'>

id возвращает уникальные идентификаторы различных объектов. Например:

id(str)
# Output: 140719548189808
id(number)
# Output: 9062912
id(some_list)
# Output: 140719528359752
id(more_list)
# Output: 140719528468696
id(float)
# Output: 140719548272000

Тип текста: str Числовые типы: int, float, complex Типы последовательностей: list, tuple, range Тип сопоставления: dict Типы наборов: set, frozenset Логический тип: bool Двоичные типы: bytes, bytearray, memoryview

Изменяемые объекты

Изменяемые и неизменяемые типы данных в Python вызывают много головной боли у начинающих программистов. Проще говоря, изменчивый означает «может быть изменен», а неизменный означает «постоянный». Хотите, чтобы у вас закружилась голова? Рассмотрим этот пример:

foo = ['hi']
print(foo)
# Output: ['hi']

bar = foo
bar += ['bye']
print(foo)
# Output: ['hi', 'bye']

Что только что произошло? Мы такого не ожидали! Мы ожидали что-то вроде этого:

foo = ['hi']
print(foo)
# Output: ['hi']

bar = foo
bar += ['bye']

print(foo)
# Expected Output: ['hi']
# Output: ['hi', 'bye']

print(bar)
# Output: ['hi', 'bye']

Это не ошибка. Это изменчивость в действии. Всякий раз, когда вы назначаете переменную другой переменной изменяемого типа данных, любые изменения данных отражаются обеими переменными. Новая переменная — это просто псевдоним старой переменной. Это верно только для изменяемых типов данных. Вот подсказка, связанная с функциями и изменяемыми типами данных:

def add_to(num, target=[]):
    target.append(num)
    return target

add_to(1)
# Output: [1]

add_to(2)
# Output: [1, 2]

add_to(3)
# Output: [1, 2, 3]

Возможно, вы ожидали, что он будет вести себя по-другому. Возможно, вы ожидаете, что новый список будет создан при вызове add_to следующим образом:

def add_to(num, target=[]):
    target.append(num)
    return target

add_to(1)
# Output: [1]

add_to(2)
# Output: [2]

add_to(3)
# Output: [3]

Ну, опять же, это изменчивость списков, которая вызывает эту боль. В Python аргументы по умолчанию оцениваются один раз при определении функции, а не при каждом вызове функции. Вы никогда не должны определять аргументы по умолчанию изменяемого типа, если вы не знаете, что делаете. Вы должны сделать что-то вроде этого:

def add_to(element, target=None):
    if target is None:
        target = []
    target.append(element)
    return target

Теперь всякий раз, когда вы вызываете функцию без аргумента target, создается новый список. Например:

add_to(42)
# Output: [42]

add_to(42)
# Output: [42]

add_to(42)
# Output: [42]

Неизменяемые объекты

Это встроенные типы, такие как int, float, bool, string, unicode, tuple. Проще говоря, неизменяемый объект нельзя изменить после его создания.

tuple1 = (0, 1, 2, 3)
tuple1[0] = 4
print(tuple1)
# Output:
Traceback (most recent call last):
  File "e0eaddff843a8695575daec34506f126.py", line 3, in 
    tuple1[0]=4
TypeError: 'tuple' object does not support item assignment
message = "Welcome to Python"
message[0] = 'p'
print(message)
# Output:
Traceback (most recent call last):
  File "/home/ff856d3c5411909530c4d328eeca165b.py", line 3, in 
    message[0] = 'p'
TypeError: 'str' object does not support item assignment

Почему это важно и насколько по-разному Python обрабатывает изменяемые и неизменяемые объекты

  • Неизменяемые объекты потокобезопасны. Состояние объекта не может быть изменено после его создания. Это означает, что данные только для чтения распределяются между потоками, что обеспечивает потокобезопасность. Операции, связанные с мутациями, например (изменение значения по определенному индексу), могут быть реализованы таким образом, что они создают новые объекты вместо изменения существующих.
  • Неизменяемые объекты улучшают правильность и ясность. Это обеспечивает разумный способ рассуждать о коде. Поскольку значение объекта не изменится на протяжении всего выполнения программы. Мы можем свободно передавать объекты, не опасаясь модификации.
  • Об изменяемых объектах гораздо сложнее рассуждать. Изменяемые объекты с несколькими ссылками, указывающими на один и тот же объект, также называемые псевдонимами, приводят к несоответствиям и угрожают правильности кода. Одна часть кода изменяет значение, а другая часть кода затрагивается из-за этой мутации. Вот почему это приводит к несоответствию.

В Python также есть некоторые оптимизации, связанные с неизменностью. Особенно для строковых объектов. Если мы создадим 10 строковых объектов с одинаковым значением, то он не будет выделять память для 10 строк, а каждый идентификатор будет ссылаться на одну и ту же строку. Таким образом, экономя память. Хотя эта вещь зависит от реализации и не будет работать точно так же для других неизменяемых типов данных.

s1 = "some test"
s2 = "Hello"
id(s1)
# Output: 140719528479728
id(s2)
# Output: 140719545351728

Как аргументы передаются функциям и что это означает для изменяемых и неизменяемых объектов

В Python почти все является объектом. Числа, строки, функции, классы, модули и даже скомпилированный код Python — все это объекты. Python рассматривает все переменные как ссылки на объект. Это означает, что все переменные хранят адрес памяти фактического объекта. Эта концепция очень похожа на «Pointer» в языках программирования C и C++. Это означает, что адрес фактического объекта хранится в именованной переменной Python, а не само значение.

Неизменные объекты Python, такие как numbers, tuple и strings, также передаются по ссылке, как и изменяемые объекты, такие как list, set и dict.. Из-за состояния неизменяемых (неизменяемых) объектов, если это целое или строковое значение изменяется внутри функционального блока, то это во многом похоже на копирование объекта. Создается и обрабатывается локальная новая копия вызывающего объекта внутри области действия функционального блока. Вызывающий объект останется неизменным. Следовательно, вызывающий блок не заметит никаких изменений, внесенных в неизменяемый объект внутри области действия функционального блока. Давайте посмотрим на следующий пример.

def updateNumber(n):
     print(id(n))
     n += 10
 
number = 5
id(number)
# Output: 9062752
updateNumber(number)
# Output: 9062752
print(number)
# Output: 5

Изменяемые объекты Python, такие как dict и list, также передаются по ссылке. Если значение изменяемого объекта изменяется внутри области действия функционального блока, то его значение также изменяется внутри области действия вызывающего или основного блока независимо от имени аргумента. Давайте посмотрим на следующий пример.

def updateList(list1):
     for index, value in enumerate(list1):
             list1[index] = 2 * value
 
list1 = [1, 3, 'Hello']
updateList(list1)
print(list1)
# Output: [2, 6, 'HelloHello']