Изменяемый и неизменяемый (и Hashable) в Python

Введение

Примеры

  • Изменчивый против неизменного

    В Python есть два типа типов. Неизменяемые типы и изменяемые типы.

    Immutables

    Объект неизменяемого типа не может быть изменен. Любая попытка изменить объект приведет к созданию копии.

    Эта категория включает в себя: целые числа, числа с плавающей запятой, сложные, строки, байты, кортежи, диапазоны и фрозенцеты.

    Чтобы подчеркнуть это свойство, давайте играть с id встроенной команды. Эта функция возвращает уникальный идентификатор объекта, переданного в качестве параметра. Если идентификатор один и тот же, это тот же объект. Если он меняется, то это другой объект. (Некоторые говорят, что это на самом деле адрес памяти объекта, но остерегайтесь их, они с темной стороны силы ...)

     >>> a = 1
    >>> id(a)
    140128142243264
    >>> a += 2
    >>> a
    3
    >>> id(a)
    140128142243328
    
     

    Хорошо, 1 не 3 ... Последние новости ... Может быть, нет. Однако об этом поведении часто забывают, когда речь идет о более сложных типах, особенно строках.

     >>> stack = "Overflow"
    >>> stack
    'Overflow'
    >>> id(stack)
    140128123955504
    >>> stack += " rocks!"
    >>> stack
    'Overflow rocks!'
    
     

    Ага! Увидеть? Мы можем изменить это!

     >>> id(stack)
    140128123911472
    
     

    Нет . Хотя, кажется , мы можем изменить строку с именем переменной stack , что мы на самом деле делаем, создает новый объект , чтобы содержать результат конкатенации. Мы одурачены, потому что при этом старый объект никуда не уходит, поэтому он уничтожается. В другой ситуации это было бы более очевидно:

     >>> stack = "Stack"
    >>> stackoverflow = stack + "Overflow"
    >>> id(stack)
    140128069348184
    >>> id(stackoverflow)
    140128123911480
    
     

    В этом случае ясно, что если мы хотим сохранить первую строку, нам нужна копия. Но так ли это очевидно для других типов?

    Упражнение

    Теперь, зная, как работают неизменяемые типы, что бы вы сказали с приведенным ниже фрагментом кода? Это мудро?

     s = ""
    for i in range(1, 1000):
        s += str(i)
        s += ","
    
    
     

    Mutables

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

    В эту категорию входят: списки, словари, байтовые массивы и наборы.

    Давайте продолжать играть с нашим маленьким id функции.

     >>> b = bytearray(b'Stack')
    >>> b
    bytearray(b'Stack')
    >>> b = bytearray(b'Stack')
    >>> id(b)
    140128030688288
    >>> b += b'Overflow'
    >>> b
    bytearray(b'StackOverflow')
    >>> id(b)
    140128030688288
    
     

    (В качестве примечания я использую байты, содержащие данные ascii, чтобы прояснить мою точку зрения, но помните, что байты не предназначены для хранения текстовых данных. Пусть сила извинит меня.)

    Что у нас есть? Мы создаем ByteArray, изменить его и используя id , мы можем гарантировать , что это тот же объект, модифицирована. Не копия этого.

    Конечно, если объект будет часто изменяться, изменяемый тип делает работу намного лучше, чем неизменяемый. К сожалению, реальность этого свойства часто забывают, когда он причиняет боль больше всего.

     >>> c = b
    >>> c += b' rocks!'
    >>> c
    bytearray(b'StackOverflow rocks!')
    
     

    Хорошо...

     >>> b
    bytearray(b'StackOverflow rocks!')
    
     

    Подожди секунду ...

     >>> id(c) == id(b)
    True
    
     

    В самом деле. c не копия b . c является b .

    Упражнение

    Теперь вы лучше понимаете, какой побочный эффект подразумевает изменчивый тип. Можете ли вы объяснить, что происходит в этом примере?

     >>> ll = [ [] ]*4 # Create a list of 4 lists to contain our results
    >>> ll
    [[], [], [], []]
    >>> ll[0].append(23) # Add result 23 to first list
    >>> ll
    [[23], [23], [23], [23]]
    >>> # Oops... 
  • Изменчивый и неизменный как аргументы

    Одним из основных вариантов использования, когда разработчику необходимо учитывать изменчивость, является передача аргументов функции. Это очень важно, потому что это будет определять способность функции изменять объекты, которые не принадлежат ее области действия, или, другими словами, если функция имеет побочные эффекты. Это также важно для понимания того, где должен быть доступен результат функции.

     >>> def list_add3(lin):
        lin += [3]
        return lin
    
    >>> a = [1, 2, 3]
    >>> b = list_add3(a)
    >>> b
    [1, 2, 3, 3]
    >>> a
    [1, 2, 3, 3]
    
     

    Здесь ошибка в том , чтобы думать , что lin , в качестве параметра функции, может быть изменен локально. Вместо этого, lin и ссылка тот же объект. a Поскольку этот объект является изменяемым, модификация производится на месте, что означает , что объект на который ссылается как lin и a модифицируется. lin на самом деле не должна быть возвращена, потому что у нас уже есть ссылка на этот объект в виде . a и b конец ссылки на тот же объект.

    Это не то же самое для кортежей.

     >>> def tuple_add3(tin):
        tin += (3,)
        return tin
    
    >>> a = (1, 2, 3)
    >>> b = tuple_add3(a)
    >>> b
    (1, 2, 3, 3)
    >>> a
    (1, 2, 3)
    
     

    В начале функции, tin и a качестве ссылки и тот же объект. Но это неизменный объект. Поэтому , когда функция пытается изменить его, tin получить новый объект с модификацией, а сохраняет ссылку на исходный объект. a В этом случае возврат tin является обязательным, или новый объект будет потерян.

    Упражнение

     >>> def yoda(prologue, sentence):
        sentence.reverse()
        prologue += " ".join(sentence)
        return prologue
    
    >>> focused = ["You must", "stay focused"]
    >>> saying = "Yoda said: "
    >>> yoda_sentence = yoda(saying, focused)
    
     

    Примечание: reverse работает на месте.

    Что вы думаете об этой функции? Есть ли у него побочные эффекты? Нужен ли возврат? После вызова, что значение saying ? Из focused ? Что происходит, если функция вызывается снова с теми же параметрами?

Синтаксис

Параметры

Примечания