O Python tem uma lista imutável?

99

O python tem listas imutáveis?

Suponha que eu deseje ter a funcionalidade de uma coleção ordenada de elementos, mas que desejo garantir que não mudará, como isso pode ser implementado? As listas são ordenadas, mas podem sofrer mutações.

cammil
fonte
4
@Marcin: Esta é uma pergunta do tipo FAQ, feita e respondida pela mesma pessoa.
RichieHindle de
@Marcin: Você obviamente não percebeu que o OP respondeu à sua própria pergunta .
Sven Marnach de
2
A principal motivação para tipos imutáveis ​​em Python é que eles podem ser usados ​​como chaves de dicionário e em conjuntos.
Sven Marnach
17
Peço desculpas se ofendi alguém aqui. Eu simplesmente procurei por listas imutáveis ​​no google e não encontrei nada. Quando descobri que o que estava procurando era uma tupla, me dei ao trabalho de publicá-la aqui. Apenas no caso de alguém ser tão "burro" quanto eu.
cammil
5
Concordo. Em retrospecto, parece estúpido, mas por alguma razão, meu cérebro idiota me levou pelo caminho errado. Tendo usado listas quase exclusivamente, e finalmente percebendo que precisava de uma lista imutável, fiz uma pergunta natural. Mesmo sabendo da existência de tuplas, não havia conectado as duas. Se isso ajudar mais alguém por aí, então eu sinto que este não é um post inútil. Se, entretanto, esta não é a resposta certa para esta pergunta simples, então isso é outra questão.
cammil

Respostas:

114

Sim. É chamado de tuple.

Portanto, em vez de [1,2]qual é a liste que pode sofrer mutação, (1,2)é a tuplee não pode.


Outras informações:

Um elemento único tuplenão pode ser instanciado por escrito (1); em vez disso, você precisa escrever (1,). Isso ocorre porque o intérprete tem vários outros usos para parênteses.

Você também pode eliminar completamente os parênteses: 1,2é o mesmo que(1,2)

Observe que uma tupla não é exatamente uma lista imutável. Clique aqui para ler mais sobre as diferenças entre listas e tuplas

cammil
fonte
6
Além disso, se você colocar ponteiros de objeto inerentemente mutáveis ​​na tupla (por exemplo ([1,2],3)), a tupla não será mais verdadeiramente imutável, porque o objeto de lista é apenas um ponteiro para um objeto mutável e, embora o ponteiro seja imutável, o objeto referenciado não é.
Nisan.H
2
Além disso, ao responder a uma pergunta tão básica, pelo menos forneça mais explicações, como as diferenças de desempenho (tupla um pouco mais rápida) e que tuplas podem ser usadas como chaves de ditado, enquanto a lista não. Tenho certeza de que também existem muitas outras diferenças.
BrtH de
3
Na verdade, uma tupla vazia também pode ser escrita (). Esse é o único caso em que os parênteses são necessários.
RemcoGerlich
1
Listas e tuplas imutáveis ​​são duas coisas muito diferentes. Tuplas são imutáveis, mas não podem ser iteradas, e você não pode mapear / reduzir / filtrar / ... em uma única tupla, mas deve ser capaz de fazer isso em uma única lista mutável / imutável. Confira outras linguagens que promovem a imutabilidade e a programação funcional mais a sério do que o python ... e você descobrirá que a lista imutável é obrigatória.
Kane
2
Uma tupla não é uma lista, eles não têm comportamento compatível, nem você pode usá-los polimorficamente.
jeremyjjbrown
8

Aqui está uma implementação de ImmutableList. A lista subjacente não é exposta em nenhum membro de dados direto. Ainda assim, ele pode ser acessado usando a propriedade closure da função de membro. Se seguirmos a convenção de não modificar o conteúdo do fechamento usando a propriedade acima, esta implementação servirá ao propósito. A instância desta classe ImmutableList pode ser usada em qualquer lugar em que uma lista Python normal seja esperada.

from functools import reduce

__author__ = 'hareesh'


class ImmutableList:
    """
    An unmodifiable List class which uses a closure to wrap the original list.
    Since nothing is truly private in python, even closures can be accessed and
    modified using the __closure__ member of a function. As, long as this is
    not done by the client, this can be considered as an unmodifiable list.

    This is a wrapper around the python list class
    which is passed in the constructor while creating an instance of this class.
    The second optional argument to the constructor 'copy_input_list' specifies
    whether to make a copy of the input list and use it to create the immutable
    list. To make the list truly immutable, this has to be set to True. The
    default value is False, which makes this a mere wrapper around the input
    list. In scenarios where the input list handle is not available to other
    pieces of code, for modification, this approach is fine. (E.g., scenarios
    where the input list is created as a local variable within a function OR
    it is a part of a library for which there is no public API to get a handle
    to the list).

    The instance of this class can be used in almost all scenarios where a
    normal python list can be used. For eg:
    01. It can be used in a for loop
    02. It can be used to access elements by index i.e. immList[i]
    03. It can be clubbed with other python lists and immutable lists. If
        lst is a python list and imm is an immutable list, the following can be
        performed to get a clubbed list:
        ret_list = lst + imm
        ret_list = imm + lst
        ret_list = imm + imm
    04. It can be multiplied by an integer to increase the size
        (imm * 4 or 4 * imm)
    05. It can be used in the slicing operator to extract sub lists (imm[3:4] or
        imm[:3] or imm[4:])
    06. The len method can be used to get the length of the immutable list.
    07. It can be compared with other immutable and python lists using the
        >, <, ==, <=, >= and != operators.
    08. Existence of an element can be checked with 'in' clause as in the case
        of normal python lists. (e.g. '2' in imm)
    09. The copy, count and index methods behave in the same manner as python
        lists.
    10. The str() method can be used to print a string representation of the
        list similar to the python list.
    """

    @staticmethod
    def _list_append(lst, val):
        """
        Private utility method used to append a value to an existing list and
        return the list itself (so that it can be used in funcutils.reduce
        method for chained invocations.

        @param lst: List to which value is to be appended
        @param val: The value to append to the list
        @return: The input list with an extra element added at the end.

        """
        lst.append(val)
        return lst

    @staticmethod
    def _methods_impl(lst, func_id, *args):
        """
        This static private method is where all the delegate methods are
        implemented. This function should be invoked with reference to the
        input list, the function id and other arguments required to
        invoke the function

        @param list: The list that the Immutable list wraps.

        @param func_id: should be the key of one of the functions listed in the
            'functions' dictionary, within the method.
        @param args: Arguments required to execute the function. Can be empty

        @return: The execution result of the function specified by the func_id
        """

        # returns iterator of the wrapped list, so that for loop and other
        # functions relying on the iterable interface can work.
        _il_iter = lambda: lst.__iter__()
        _il_get_item = lambda: lst[args[0]]  # index access method.
        _il_len = lambda: len(lst)  # length of the list
        _il_str = lambda: lst.__str__()  # string function
        # Following represent the >, < , >=, <=, ==, != operators.
        _il_gt = lambda: lst.__gt__(args[0])
        _il_lt = lambda: lst.__lt__(args[0])
        _il_ge = lambda: lst.__ge__(args[0])
        _il_le = lambda: lst.__le__(args[0])
        _il_eq = lambda: lst.__eq__(args[0])
        _il_ne = lambda: lst.__ne__(args[0])
        # The following is to check for existence of an element with the
        # in clause.
        _il_contains = lambda: lst.__contains__(args[0])
        # * operator with an integer to multiply the list size.
        _il_mul = lambda: lst.__mul__(args[0])
        # + operator to merge with another list and return a new merged
        # python list.
        _il_add = lambda: reduce(
            lambda x, y: ImmutableList._list_append(x, y), args[0], list(lst))
        # Reverse + operator, to have python list as the first operand of the
        # + operator.
        _il_radd = lambda: reduce(
            lambda x, y: ImmutableList._list_append(x, y), lst, list(args[0]))
        # Reverse * operator. (same as the * operator)
        _il_rmul = lambda: lst.__mul__(args[0])
        # Copy, count and index methods.
        _il_copy = lambda: lst.copy()
        _il_count = lambda: lst.count(args[0])
        _il_index = lambda: lst.index(
            args[0], args[1], args[2] if args[2] else len(lst))

        functions = {0: _il_iter, 1: _il_get_item, 2: _il_len, 3: _il_str,
                     4: _il_gt, 5: _il_lt, 6: _il_ge, 7: _il_le, 8: _il_eq,
                     9: _il_ne, 10: _il_contains, 11: _il_add, 12: _il_mul,
                     13: _il_radd, 14: _il_rmul, 15: _il_copy, 16: _il_count,
                     17: _il_index}

        return functions[func_id]()

    def __init__(self, input_lst, copy_input_list=False):
        """
        Constructor of the Immutable list. Creates a dynamic function/closure
        that wraps the input list, which can be later passed to the
        _methods_impl static method defined above. This is
        required to avoid maintaining the input list as a data member, to
        prevent the caller from accessing and modifying it.

        @param input_lst: The input list to be wrapped by the Immutable list.
        @param copy_input_list: specifies whether to clone the input list and
            use the clone in the instance. See class documentation for more
            details.
        @return:
        """

        assert(isinstance(input_lst, list))
        lst = list(input_lst) if copy_input_list else input_lst
        self._delegate_fn = lambda func_id, *args: \
            ImmutableList._methods_impl(lst, func_id, *args)

    # All overridden methods.
    def __iter__(self): return self._delegate_fn(0)

    def __getitem__(self, index): return self._delegate_fn(1, index)

    def __len__(self): return self._delegate_fn(2)

    def __str__(self): return self._delegate_fn(3)

    def __gt__(self, other): return self._delegate_fn(4, other)

    def __lt__(self, other): return self._delegate_fn(5, other)

    def __ge__(self, other): return self._delegate_fn(6, other)

    def __le__(self, other): return self._delegate_fn(7, other)

    def __eq__(self, other): return self._delegate_fn(8, other)

    def __ne__(self, other): return self._delegate_fn(9, other)

    def __contains__(self, item): return self._delegate_fn(10, item)

    def __add__(self, other): return self._delegate_fn(11, other)

    def __mul__(self, other): return self._delegate_fn(12, other)

    def __radd__(self, other): return self._delegate_fn(13, other)

    def __rmul__(self, other): return self._delegate_fn(14, other)

    def copy(self): return self._delegate_fn(15)

    def count(self, value): return self._delegate_fn(16, value)

    def index(self, value, start=0, stop=0):
        return self._delegate_fn(17, value, start, stop)


def main():
    lst1 = ['a', 'b', 'c']
    lst2 = ['p', 'q', 'r', 's']

    imm1 = ImmutableList(lst1)
    imm2 = ImmutableList(lst2)

    print('Imm1 = ' + str(imm1))
    print('Imm2 = ' + str(imm2))

    add_lst1 = lst1 + imm1
    print('Liist + Immutable List: ' + str(add_lst1))
    add_lst2 = imm1 + lst2
    print('Immutable List + List: ' + str(add_lst2))
    add_lst3 = imm1 + imm2
    print('Immutable Liist + Immutable List: ' + str(add_lst3))

    is_in_list = 'a' in lst1
    print("Is 'a' in lst1 ? " + str(is_in_list))

    slice1 = imm1[2:]
    slice2 = imm2[2:4]
    slice3 = imm2[:3]
    print('Slice 1: ' + str(slice1))
    print('Slice 2: ' + str(slice2))
    print('Slice 3: ' + str(slice3))

    imm1_times_3 = imm1 * 3
    print('Imm1 Times 3 = ' + str(imm1_times_3))
    three_times_imm2 = 3 * imm2
    print('3 Times Imm2 = ' + str(three_times_imm2))

    # For loop
    print('Imm1 in For Loop: ', end=' ')
    for x in imm1:
        print(x, end=' ')
    print()

    print("3rd Element in Imm1: '" + imm1[2] + "'")

    # Compare lst1 and imm1
    lst1_eq_imm1 = lst1 == imm1
    print("Are lst1 and imm1 equal? " + str(lst1_eq_imm1))

    imm2_eq_lst1 = imm2 == lst1
    print("Are imm2 and lst1 equal? " + str(imm2_eq_lst1))

    imm2_not_eq_lst1 = imm2 != lst1
    print("Are imm2 and lst1 different? " + str(imm2_not_eq_lst1))

    # Finally print the immutable lists again.
    print("Imm1 = " + str(imm1))
    print("Imm2 = " + str(imm2))

    # The following statemetns will give errors.
    # imm1[3] = 'h'
    # print(imm1)
    # imm1.append('d')
    # print(imm1)

if __name__ == '__main__':
    main()
hareesh
fonte
6

Você pode simular uma lista imutável unida por um único estilo Lisp usando tuplas de dois elementos (nota: isso é diferente da resposta da tupla de qualquer elemento , que cria uma tupla que é muito menos flexível):

nil = ()
cons = lambda ele, l: (ele, l)

por exemplo, para a lista [1, 2, 3], você teria o seguinte:

l = cons(1, cons(2, cons(3, nil))) # (1, (2, (3, ())))

Seu padrão care cdrfunções são simples:

car = lambda l: l[0]
cdr = lambda l: l[1]

Uma vez que esta lista está unida individualmente, anexando à frente está O (1). Como essa lista é imutável, se os elementos subjacentes na lista também forem imutáveis, você poderá compartilhar com segurança qualquer sublista a ser reutilizada em outra lista.

kevinji
fonte
Como essa implementação é mais flexível do que a tupla nativa (a, b, c)?
Literal
@Literal Você é capaz de preceder a uma lista unida por um único, ao contrário de uma tupla regular, que está congelada. Isso é o que os torna muito mais versáteis e básicos em linguagens de programação funcionais.
kevinji
1
Obrigado pela sua resposta. Ainda estou tentando entender o benefício dessa implementação, visto que também posso acrescentar um elemento ao criar uma nova instância de tupla: (z,) + (a, b, c). É uma questão de desempenho?
Literal de
@Literal Sim. No seu caso, concatenar um elemento é O (n) com n sendo o comprimento da lista existente, já que você precisa alocar uma nova tupla de tamanho n + 1 e então copiar os elementos. Aqui, concat é O (1), pois você só precisa alocar uma tupla de tamanho 2 com um ponteiro para a "lista" original.
kevinji
4

Mas se houver uma tupla de arrays e tuplas, então a matriz dentro de uma tupla pode ser modificada.

>>> a
([1, 2, 3], (4, 5, 6))

>>> a[0][0] = 'one'

>>> a
(['one', 2, 3], (4, 5, 6))
Gopal
fonte
9
Não pode haver realmente uma coleção que torne seu conteúdo imutável, porque você precisaria de uma maneira de fazer uma cópia imutável de objetos arbitrários. Para fazer isso, você teria que copiar as classes a que esses objetos pertencem e até mesmo as classes internas às quais eles fazem referência. E ainda, os objetos podem se referir ao sistema de arquivos, ou à rede, ou qualquer outra coisa que sempre será mutável. Portanto, uma vez que não podemos tornar um objeto arbitrário imutável, temos que nos contentar com coleções imutáveis ​​de objetos mutáveis.
Jack O'Connor
2
@ JackO'Connor Não concordo totalmente. Tudo depende de como você modela o mundo: a mutabilidade externa sempre pode ser modelada como estados que evoluem no tempo e, em vez de manter um único estado mutável s, sempre posso escolher me referir a s_t, que é imutável. "Coleção imutável de objetos imutáveis" <- verifique Huskell, Scala e outras linguagens de programação funcionais. Antes de começar a aprender Python, costumava acreditar que Python tem suporte total de imutabilidade e fp pelo que ouvi de outras pessoas, mas isso não é verdade.
Kane
Eu deveria ter dito, não pode realmente haver tal coisa em Python. A imutabilidade do Python depende do programador respeitar as convenções (como _private_variables), ao invés de qualquer imposição do interpretador.
Jack O'Connor
1
Uma linguagem como Haskell oferece muito mais garantias, embora se o programador realmente quisesse ser mau, ele ainda poderia escrever /proc/#/memou vincular a bibliotecas inseguras ou qualquer outra coisa para quebrar o modelo.
Jack O'Connor
1

Lista e Tupla têm uma diferença em seu estilo de trabalho.

Em LIST, podemos fazer alterações após sua criação, mas se você quiser uma sequência ordenada em que nenhuma alteração possa ser aplicada no futuro, você pode usar TUPLE.

Outras informações::

 1) the LIST is mutable that means you can make changes in it after its creation
 2) In Tuple, we can not make changes once it created
 3) the List syntax is
           abcd=[1,'avn',3,2.0]
 4) the syntax for Tuple is 
           abcd=(1,'avn',3,2.0) 
      or   abcd= 1,'avn',3,2.0 it is also correct
Avnish Kumar
fonte
-2

Em vez de tupla, você pode usar frozenset. frozenset cria um conjunto imutável. você pode usar list como membro do frozenset e acessar todos os elementos da lista dentro do frozenset usando um único loop for.

Vishal Mopari
fonte
4
O frozenset requer que seus membros de conjunto sejam hashable, o que uma lista não é.
matias elgart