¿Python tiene una lista inmutable?

14 minutos de lectura

avatar de usuario
camil

¿Python tiene listas inmutables?

Supongamos que deseo tener la funcionalidad de una colección ordenada de elementos, pero que quiero garantizar que no cambiará, ¿cómo se puede implementar esto? Las listas están ordenadas pero se pueden mutar.

  • La principal motivación para los tipos inmutables en Python es que se pueden usar como claves de diccionario y en conjuntos.

    – Sven Marnach

    21 de junio de 2012 a las 16:22

avatar de usuario
camil

Sí. se llama un tuple.

Entonces, en lugar de [1,2] el cual es un list y que puede ser mutado, (1,2) es un tuple y no puede.


Más información:

un elemento tuple no se puede instanciar escribiendo (1)en cambio, necesitas escribir (1,). Esto se debe a que el intérprete tiene otros usos para los paréntesis.

También puede eliminar los paréntesis por completo: 1,2 es lo mismo que (1,2)

Tenga en cuenta que una tupla no es exactamente una lista inmutable. Haga clic aquí para leer más sobre las diferencias entre listas y tuplas

  • Además, si coloca punteros de objetos intrínsecamente mutables en la tupla (por ejemplo, ([1,2],3)), la tupla ya no es realmente inmutable, porque el objeto de la lista es solo un puntero a un objeto mutable y, aunque el puntero es inmutable, el objeto al que se hace referencia no lo es.

    – Nisan.H

    21 de junio de 2012 a las 16:21


  • En realidad, también se puede escribir una tupla vacía (). Ese es el único caso donde se requieren los paréntesis.

    – RemcoGerlich

    22 de septiembre de 2015 a las 9:43

  • Las listas inmutables y las tuplas son dos cosas muy diferentes. Las tuplas son inmutables, pero no se pueden iterar, y no puede mapear/reducir/filtrar/… en una sola tupla, pero debería poder hacerlo en una sola lista mutable/inmutable. Consulte otros lenguajes que promuevan la inmutabilidad y la programación funcional con más seriedad que Python… y encontrará que la lista inmutable es imprescindible.

    – Kane

    29 de septiembre de 2015 a las 14:49

  • Una tupla no es una Lista, no tienen un comportamiento compatible, ni puedes usarlas polimórficamente.

    – jeremyjjbrown

    5 de enero de 2016 a las 15:57

  • Este es uno de los grandes problemas de Python: es tan simple de usar que nadie lo usa correctamente. (Y si intenta usarlo correctamente, descubre que no puede). La pregunta era si existe una lista inmutable en Python. La respuesta aceptada fue “usar una tupla”. Pero casi todos los que entienden la semántica están de acuerdo en que una tupla no es una lista. (Guido lo dijo él mismo.) Entonces, al final del día, la pregunta no tiene respuesta: ¿cómo puedo obtener una lista inmutable, el análogo de frozenset para el tipo de conjunto?

    – Garret Wilson

    17/10/2016 a las 22:12

avatar de usuario
hareesh

Aquí hay un ImmutableList implementación. La lista subyacente no se expone en ningún miembro de datos directo. Aún así, se puede acceder a él usando el cierre propiedad de la función miembro. Si seguimos la convención de no modificar el contenido del cierre usando la propiedad anterior, esta implementación cumplirá el propósito. instancia de esto ImmutableList La clase se puede usar en cualquier lugar donde se espere una lista de python normal.

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()

Puede simular una lista inmutable de un solo enlace al estilo Lisp usando tuplas de dos elementos (nota: esto es diferente a la respuesta de tupla de cualquier elemento, que crea una tupla que es mucho menos flexible):

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

por ejemplo, para la lista [1, 2, 3]tendrías lo siguiente:

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

tu estándar car y cdr Las funciones son sencillas:

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

Dado que esta lista está unida de forma sencilla, se agrega al frente O(1). Dado que esta lista es inmutable, si los elementos subyacentes de la lista también son inmutables, puede compartir de forma segura cualquier sublista para reutilizarla en otra lista.

  • ¿Cómo es esta implementación más flexible que la tupla nativa (a,b,c)?

    – literal

    6 de noviembre de 2020 a las 21:19

  • @Literal Puede anteponer una lista con un solo enlace, a diferencia de una tupla normal, que está congelada. Esto es lo que los hace mucho más versátiles y un elemento básico en los lenguajes de programación funcionales.

    -Kevinji

    8 de noviembre de 2020 a las 22:23

  • Gracias por su respuesta. Todavía estoy tratando de entender el beneficio de esta implementación, ya que también puedo anteponer un elemento creando una nueva instancia de tupla: (z,) + (a,b,c). ¿Es una cuestión de rendimiento?

    – literal

    9 de noviembre de 2020 a las 13:27

  • @Literal Sí. En su caso, concatenar un elemento es O(n) siendo n la longitud de la lista existente, ya que debe asignar una nueva tupla de tamaño n+1 y luego copiar los elementos. Aquí, concat es O (1) ya que solo necesita asignar una tupla de tamaño 2 con un puntero a la “lista” original.

    -Kevinji

    3 de diciembre de 2020 a las 17:36

avatar de usuario
Gopal

Pero si hay una tupla de matrices y tuplas, entonces se puede modificar la matriz dentro de una tupla.

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

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

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

avatar de usuario
bluenote10

Esta pregunta merece una respuesta moderna, ahora que escribe anotaciones y escribe verificación a través de mypy son cada vez más populares.

Reemplazo de un List[T] por una tupla puede no ser la solución ideal cuando se utilizan anotaciones de tipo. Conceptualmente una lista tiene una aridad genérica de 1, es decir, tienen un solo argumento genérico T (por supuesto, este argumento puede ser un Union[A, B, C, ...] para dar cuenta de listas heterogéneamente tipadas). En contraste, las tuplas son genéricos intrínsecamente variados. Tuple[A, B, C, ...]. Esto hace que las tuplas sean un reemplazo de lista incómodo.

De hecho, la verificación de tipos ofrece otra posibilidad: es posible anotar variables como listas inmutables usando typing.Sequenceque corresponde al tipo de la interfaz inmutable collections.abc.Sequence. Por ejemplo:

from typing import Sequence


def f(immutable_list: Sequence[str]) -> None:
    # We want to prevent mutations like:
    immutable_list.append("something")


mutable_list = ["a", "b", "c"]
f(mutable_list)
print(mutable_list)

Por supuesto, en términos de comportamiento en tiempo de ejecución, esto no es inmutable, es decir, el intérprete de Python felizmente mutará immutable_listy la salida sería ["a", "b", "c", "something"].

Sin embargo, si su proyecto usa un verificador de tipo como mypyrechazará el código con:

immutable_lists_1.py:6: error: "Sequence[str]" has no attribute "append"
Found 1 error in 1 file (checked 1 source file)

Por lo tanto, bajo el capó, puede continuar usando listas regulares, pero el verificador de tipos puede prevenir efectivamente cualquier mutación en el momento de la verificación de tipos.

De manera similar, podría evitar modificaciones de los miembros de la lista, por ejemplo, en clases de datos inmutables:

@dataclass(frozen=True)
class ImmutableData:
    immutable_list: Sequence[str]


def f(immutable_data: ImmutableData) -> None:
    # mypy will prevent mutations here as well:
    immutable_data.immutable_list.append("something")

El mismo principio se puede utilizar para dictados a través de typing.Mapping.

  • Sequence no lo marca exactamente como inmutable, solo significa que no tiene métodos disponibles que pueden mutarlo. La variable subyacente aún puede ser mutable.

    – Timmmmm

    16 de diciembre de 2021 a las 11:13

  • @Timmmm Lo sé, por eso he escrito: Por supuesto, en términos de comportamiento en tiempo de ejecución, esto no es inmutable, es decir, el intérprete de Python felizmente mutará immutable_list. Si su CI aplica verificaciones de tipo estrictas, lo que hacen muchos proyectos en estos días, se vuelve relativamente seguro.

    – bluenote10

    16 de diciembre de 2021 a las 13:12


List y Tuple tienen una diferencia en su estilo de trabajo.

En LIST podemos hacer cambios después de su creación pero si quieres una secuencia ordenada en la que no se puedan aplicar cambios en el futuro puedes usar TUPLE.

más información::

 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

  • Sequence no lo marca exactamente como inmutable, solo significa que no tiene métodos disponibles que pueden mutarlo. La variable subyacente aún puede ser mutable.

    – Timmmmm

    16 de diciembre de 2021 a las 11:13

  • @Timmmm Lo sé, por eso he escrito: Por supuesto, en términos de comportamiento en tiempo de ejecución, esto no es inmutable, es decir, el intérprete de Python felizmente mutará immutable_list. Si su CI aplica verificaciones de tipo estrictas, lo que hacen muchos proyectos en estos días, se vuelve relativamente seguro.

    – bluenote10

    16 de diciembre de 2021 a las 13:12


En lugar de tupla, puede usar frozenset. frozenset crea un conjunto inmutable. puede usar la lista como miembro de frozenset y acceder a cada elemento de la lista dentro de frozenset usando un solo bucle for.

  • frozenset requiere que los miembros de su conjunto sean hashable, lo que no es una lista.

    – matías elgart

    18/11/2016 a las 15:20

¿Ha sido útil esta solución?