Python a-t-il une liste immuable?

93

Python a-t-il des listes immuables?

Supposons que je souhaite avoir la fonctionnalité d'une collection ordonnée d'éléments, mais que je veux garantir ne changera pas, comment cela peut-il être implémenté? Les listes sont ordonnées mais elles peuvent être mutées.

cammil
la source
4
@Marcin: C'est une question de style FAQ, posée et répondue par la même personne.
RichieHindle
@Marcin: Vous n'avez évidemment pas remarqué que l'OP a répondu à sa propre question .
Sven Marnach
2
La principale motivation des types immuables en Python est qu'ils peuvent être utilisés comme clés de dictionnaire et dans des ensembles.
Sven Marnach
16
Toutes mes excuses si j'ai offensé quelqu'un ici. J'ai simplement cherché des listes immuables sur Google et je n'ai rien trouvé. Quand j'ai compris que ce que je cherchais était un tuple, j'ai pris la peine de le publier ici. Juste au cas où quelqu'un serait aussi "idiot" que moi.
cammil
5
Je suis d'accord. Avec le recul, cela semble stupide, mais pour une raison quelconque, mon cerveau stupide m'avait conduit sur la mauvaise voie. Ayant utilisé presque exclusivement des listes, et réalisant finalement que j'avais besoin d'une liste immuable, j'ai posé une question naturelle. Même si j'étais bien conscient de l'existence de tuples, je n'avais pas connecté les deux. Si cela aide quelqu'un d'autre, je pense que ce n'est pas un message inutile. Si toutefois ce n’est pas la bonne réponse à cette simple question, alors c’est une tout autre affaire.
cammil

Réponses:

108

Oui. Cela s'appelle un tuple.

Donc, au lieu de [1,2]qui est a listet qui peut être muté, (1,2)est a tupleet ne peut pas.


Informations complémentaires:

Un élément unique tuplene peut pas être instancié par écriture (1), mais vous devez écrire (1,). C'est parce que l'interpréteur a diverses autres utilisations pour les parenthèses.

Vous pouvez également supprimer complètement les parenthèses: 1,2c'est la même chose que(1,2)

Notez qu'un tuple n'est pas exactement une liste immuable. Cliquez ici pour en savoir plus sur les différences entre les listes et les tuples

cammil
la source
6
De plus, si vous placez des pointeurs d'objet mutables de manière inhérente dans le tuple (par exemple ([1,2],3)), le tuple n'est plus vraiment immuable, car l'objet de liste est juste un pointeur vers un objet mutable, et alors que le pointeur est immuable, l'objet référencé ne l'est pas.
Nisan.H
2
aussi, lorsque vous répondez à une question aussi fondamentale, fournissez au moins des explications supplémentaires, telles que les différences de performances (tuple légèrement plus rapide) et que les tuples peuvent être utilisés comme clés de dict, alors que list ne peut pas. Je suis sûr qu'il y a aussi beaucoup d'autres différences.
BrtH
3
En fait, un tuple vide peut également être écrit (). C'est le seul cas où les parenthèses sont obligatoires.
RemcoGerlich
1
@Kane, votre affirmation est certainement vraie dans les langages fonctionnels typés; spécifiquement, (3,4,5)a un type très différent - (int x int x int)—que [3,4,5], qui a un type (listof int). Cependant, le tuple de python semble vraiment plus proche d'une liste immuable: en particulier, ils peuvent être itérés, et il semble qu'ils peuvent également être filtrés et mappés.
John Clements
1
Un tuple n'est pas une liste, ils n'ont pas de comportement compatible et vous ne pouvez pas non plus les utiliser de manière polymorphe.
jeremyjjbrown
7

Voici une implémentation ImmutableList. La liste sous-jacente n'est exposée dans aucun membre de données direct. Néanmoins, il est accessible à l'aide de la propriété de fermeture de la fonction membre. Si nous suivons la convention de ne pas modifier le contenu de la fermeture en utilisant la propriété ci-dessus, cette implémentation servira l'objectif. L'instance de cette classe ImmutableList peut être utilisée partout où une liste Python normale est attendue.

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
la source
6

Vous pouvez simuler une liste à liaison unique immuable de style Lisp en utilisant des tuples à deux éléments (note: ceci est différent de la réponse de tuple à tout élément , qui crée un tuple qui est beaucoup moins flexible):

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

par exemple pour la liste [1, 2, 3], vous auriez ce qui suit:

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

Votre standard caret vos cdrfonctions sont simples:

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

Puisque cette liste est liée individuellement, l'ajout à l'avant est O (1). Étant donné que cette liste est immuable, si les éléments sous-jacents de la liste sont également immuables, vous pouvez partager en toute sécurité n'importe quelle sous-liste à réutiliser dans une autre liste.

kevinji
la source
En quoi cette implémentation est-elle plus flexible que le tuple natif (a, b, c)?
Littéral
@Literal Vous pouvez ajouter un préfixe à une liste à lien unique, contrairement à un tuple normal, qui est figé. C'est ce qui les rend beaucoup plus polyvalents et incontournables dans les langages de programmation fonctionnels.
kevinji
Merci pour votre réponse. J'essaie toujours de comprendre l'avantage de cette implémentation, car je peux également ajouter un élément en créant une nouvelle instance de tuple: (z,) + (a, b, c). Est-ce une question de performance?
Littéral
4

Mais s'il existe un tuple de tableaux et de tuples, alors le tableau à l'intérieur d'un tuple peut être modifié.

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

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

>>> a
(['one', 2, 3], (4, 5, 6))
Gopal
la source
9
Il ne peut pas vraiment y avoir de collection qui rend son contenu immuable, car vous auriez besoin d'un moyen de faire une copie immuable d'objets arbitraires. Pour ce faire, vous devez copier les classes auxquelles appartiennent ces objets, et même les classes intégrées auxquelles ils font référence. Et pourtant, les objets peuvent faire référence au système de fichiers, au réseau ou à quelque chose d'autre qui sera toujours modifiable. Donc, comme nous ne pouvons pas rendre un objet arbitraire immuable, nous devons nous contenter de collections immuables d'objets mutables.
Jack O'Connor
1
@ JackO'Connor Pas tout à fait d'accord. Tout dépend de la façon dont vous modélisez le monde: la mutabilité externe peut toujours être modélisée comme des états évoluant dans le temps, et au lieu de maintenir un seul état mutable, je peux toujours choisir de me référer à s_t qui est immuable. "Collection immuable d'objets immuables" <- consultez Huskell, Scala et d'autres langages de programmation fonctionnels. Avant de commencer à apprendre Python, je pensais que Python supportait pleinement l'immuabilité et la fp d'après ce que j'avais entendu des autres, mais cela s'avère faux.
Kane
J'aurais dû dire, il ne peut pas vraiment y avoir une telle chose en Python. L'immuabilité de Python repose sur le respect par le programmeur des conventions (comme _private_variables), plutôt que sur toute application de l'interpréteur.
Jack O'Connor
1
Un langage comme Haskell offre beaucoup plus de garanties, bien que si le programmeur voulait vraiment être méchant, il pouvait toujours écrire /proc/#/memou se lier à des bibliothèques non sécurisées ou autre pour casser le modèle.
Jack O'Connor
1

List et Tuple ont une différence dans leur style de travail.

Dans LIST, nous pouvons apporter des modifications après sa création, mais si vous voulez une séquence ordonnée dans laquelle aucune modification ne peut être appliquée à l'avenir, vous pouvez utiliser TUPLE.

plus d'informations:

 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
la source
-1

Au lieu de tuple, vous pouvez utiliser frozenset. frozenset crée un ensemble immuable. vous pouvez utiliser list en tant que membre de frozenset et accéder à tous les éléments de la liste à l'intérieur de frozenset en utilisant une seule boucle for.

Vishal Mopari
la source
3
frozenset exige que ses membres d'ensemble soient hachables, ce qui n'est pas le cas d'une liste.
matias elgart