Python是否有一个不变的列表?


93

python是否有不可变的列表?

假设我希望具有元素的有序集合的功能,但是我想保证不会改变,那么如何实现呢?列表是有序的,但可以更改。


4
@Marcin:这是一个常见问题解答样式的问题,由同一个人提出并回答。
RichieHindle 2012年

@Marcin:您显然没有注意到OP回答了她自己的问题
Sven Marnach 2012年

2
Python中不可变类型的主要动机是它们可用作字典键和集合。
Sven Marnach 2012年

16
抱歉,如果我在这里冒犯了任何人。我只是在Google上搜索了不可变列表,却一无所获。当我发现所要查找的是一个元组时,我很麻烦在这里发布它。以防万一有人像我一样“愚蠢”。
cammil 2012年

5
我同意。事后看来,这似乎是愚蠢的,但是由于任何原因,我的愚蠢的大脑使我走错了路。有了几乎专用的列表,最后意识到我需要一个不变的列表,我问了一个自然的问题。即使我很清楚元组的存在,我也没有将两者连接起来。如果这对其他人有帮助,那么我觉得这不是一个无用的文章。但是,如果这不是对这个简单问题的正确答案,那么这完全是另一回事。
cammil 2012年

Answers:


107

是。称为tuple

所以,相反的[1,2]是一个list和可以突变,(1,2)tuple,不能。


更多的信息:

tuple不能通过编写来实例化一个元素(1),而是需要编写(1,)。这是因为解释器在括号中还有其他用途。

您也可以完全取消括号:1,2(1,2)

请注意,元组不是完全不可变的列表。单击此处以了解有关列表和元组之间差异的更多信息


6
另外,如果将固有可变对象指针放置在元组中(例如([1,2],3)),则元组不再真正不可变,因为列表对象只是指向可变对象的指针,而当指针不可变时,引用对象则不是。
Nisan.H 2012年

2
同样,当您回答这样的基本问题时,至少要提供一些更多的解释,例如性能差异(元组快一些),并且元组可以用作字典键,而list不能。我敢肯定,还有很多其他差​​异。
BrtH 2012年

3
实际上,也可以写一个空的元组()。这是需要括号的一种情况。
RemcoGerlich 2015年

1
@Kane,您的声明在类型化函数语言中肯定是正确的;具体地,(3,4,5)具有非常不同的类型- (int x int x int)-than [3,4,5],其具有类型(listof int)。但是,python的元组确实确实看起来更接近一个不可变的列表:具体地说,它们可以被迭代,并且看起来它们也可以被过滤和映射。
约翰·克莱门茨

1
元组不是List,它们没有兼容的行为,也不能多态使用它们。
jeremyjjbrown

7

这是一个ImmutableList实现。基础列表未在任何直接数据成员中公开。仍然可以使用成员函数的闭包属性对其进行访问。如果遵循不使用上述属性修改闭包内容的约定,则此实现将达到目的。该ImmutableList类的实例可以在需要正常python列表的任何地方使用。

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

5

您可以使用两个元素的元组模拟Lisp样式的不可变单链列表(注意:这与任何元素的元组answer都不同,后者会创建一个灵活性较差的元组):

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

例如,对于list [1, 2, 3],您将具有以下内容:

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

您的标准carcdr功能非常简单:

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

由于此列表是单链链接,因此附加在最前面的是O(1)。由于此列表是不可变的,因此如果列表中的基础元素也是不可变的,则可以安全地共享任何子列表以在另一个列表中重用。


4

但是,如果有一个数组和一个元组,则可以修改一个元组内的数组。

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

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

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

9
真正不可能有一个使它的内容不可变的集合,因为您需要一种使任意对象的不可变副本的方法。为此,您必须复制那些对象所属的类,甚至是它们引用的内置类。而且,对象仍然可以引用文件系统,网络或其他总是可变的东西。因此,由于我们不能使任意对象不可变,因此必须对可变对象的不可变集合感到满意。
杰克·奥康纳2014年

1
@ JackO'Connor不完全同意。这完全取决于您对世界建模的方式:外部可变性总是可以建模为随时间演变的状态,而不是维持单个可变状态s,我始终可以选择引用不可变的s_t。“不可变对象的不可变集合” <-查看Huskell,Scala和其他功能编程语言。在我开始学习Python之前,我曾经相信Python从我从别人那里得到的信息完全支持不变性和fp,但是事实证明这并非事实。
凯恩

我应该说,Python中真的不可能有这样的东西。Python的不变性依赖于程序员遵守约定(例如_private_variables),而不是解释器的任何强制要求。
杰克·奥康纳

1
诸如Haskell之类的语言可以提供更多的保证,尽管如果程序员真的想作恶,他们仍然可以写入/proc/#/mem或链接到不安全的库或破坏模型的任何内容。
杰克·奥康纳

1

List和Tuple在工作方式上有所不同。

在LIST中,我们可以在创建后进行更改,但是如果您希望有序的序列在将来无法应用更改,则可以使用TUPLE。

更多的信息::

 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

-1

除了使用元组,还可以使用Frozenset。Frozenset创建一个不可变的集合。您可以将list用作Frozenset的成员,并使用single for循环访问Frozenset内list的每个元素。


3
Frozenset要求其set成员可哈希,而列表则不可。
matias elgart
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.