Python有一个不可变的列表吗?

cam*_*mil 76 python

python有不可变列表吗?

假设我希望拥有有序元素集合的功能,但我想保证不会改变,如何实现?列表是有序的,但它们可以变异.

cam*_*mil 90

是.它叫做a tuple.

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


更多的信息:

一个元素tuple不能通过编写实例化(1),而是需要编写(1,).这是因为解释器具有括号的各种其他用途.

你也可以完全取消括号:1,2与...相同(1,2)

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

  • 我想补充一点,创建一个1元素元组你可以做`(el,)`(`(el)`不起作用)和一个空元组调用`tuple()`. (5认同)
  • 此外,如果你在元组中放置固有的可变对象指针(例如`([1,2],3)`),元组不再是真正不可变的,因为列表对象只是指向可变对象的指针,而指针是不可变的,引用的对象不是. (5认同)
  • 元组不是列表,它们没有兼容的行为,也不能多态地使用它们。 (4认同)
  • @JPvdMerwe也是,括号不需要创建元组,只需要逗号. (3认同)
  • 不可变列表和元组是两个非常不同的东西。元组是不可变的,但它们不能被迭代,你不能在单个元组上执行 map/reduce/filter/...,但你应该能够在单个可变/不可变列表上执行此操作。查看比python更认真地促进不变性和函数式编程的其他语言......你会发现不可变列表是必须的。 (3认同)
  • @Kane,您的陈述在类型化函数语言中当然是正确的;具体来说,`(3,4,5)` 的类型与 `[3,4,5]` 的类型非常不同,即 `(int x int x int)`,后者的类型为 `(listof int)`。然而,python 的元组确实看起来更接近于不可变列表:具体来说,它们*可以*被迭代,而且看起来它们也可以被过滤和映射。 (3认同)
  • 另外,当你回答这样一个基本问题时,至少提供一些更多的解释,例如性能差异(元组稍快一些),元组可以用作dict键,而list则不能.我确信还有很多其他的差异. (2认同)
  • 实际上一个空元组也可以写成`()`.这是需要括号的一种情况. (2认同)
  • 这是 Python 的一大问题:它使用起来非常简单,以至于没有人正确使用它。(如果你尝试正确使用它,你会发现你不能。)问题是 Python 中是否存在不可变列表。接受的答案是“使用元组”。但是几乎所有了解语义的人都同意元组不是列表。(Guido [自己这么说](http://code.activestate.com/lists/python-dev/33849/)。)所以在一天结束时,这个问题没有答案:我怎样才能得到一个不可变的列表,集合类型的“frozenset”的模拟? (2认同)

blu*_*e10 25

这个问题值得一个现代的答案,因为类型注释和类型检查通过mypy变得越来越流行。

使用类型注释时,用元组替换 aList[T]可能不是理想的解决方案。从概念上讲,列表的泛型参数为 1,即它们具有单个泛型参数T(当然,该参数可以是Union[A, B, C, ...]用于解释异构类型列表的 a)。相反,元组本质上是可变参数泛型Tuple[A, B, C, ...]。这使得元组成为一个尴尬的列表替代品。

事实上,类型检查提供了另一种可能性:可以通过使用将变量注释为不可变列表typing.Sequence,它对应于不可变接口的类型collections.abc.Sequence。例如:

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)
Run Code Online (Sandbox Code Playgroud)

当然,就运行时行为而言,这不是一成不变的,即,Python 解释器会很乐意改变immutable_list,并且输出将是["a", "b", "c", "something"]

但是,如果您的项目使用类似的类型检查器mypy,它将拒绝代码:

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

因此,在幕后,您可以继续使用常规列表,但类型检查器可以有效地防止类型检查时的任何突变。

类似地,您可以阻止对列表成员的修改,例如在不可变数据类中(请注意,数据类上的字段分配frozen实际上在运行时阻止的):

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

相同的原理可以用于通过 的 dict typing.Mapping

  • @Timmmm我知道,这就是我写的原因:_当然,就运行时行为而言,这不是一成不变的,即Python解释器会很乐意改变`immutable_list`_。如果您的 CI 强制执行严格的类型检查(现在许多项目都这样做),那么它就会变得相对安全。 (3认同)
  • 对于另一个问题,这实际上是一个非常好的答案。 (2认同)

小智 9

这是一个 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()
Run Code Online (Sandbox Code Playgroud)


kev*_*nji 7

您可以使用二元元组模拟 Lisp 风格的不可变单向链表(注意:这与 any-element 元组 answer 不同,后者创建的元组不太灵活):

nil = ()
cons = lambda ele, l: (ele, l)
Run Code Online (Sandbox Code Playgroud)

例如,对于列表[1, 2, 3],您将拥有以下内容:

l = cons(1, cons(2, cons(3, nil))) # (1, (2, (3, ())))
Run Code Online (Sandbox Code Playgroud)

你的标准 carcdr功能很简单:

car = lambda l: l[0]
cdr = lambda l: l[1]
Run Code Online (Sandbox Code Playgroud)

由于此列表是单链接的,因此追加到前面是 O(1)。由于这个列表是不可变的,如果列表中的底层元素也是不可变的,那么您可以安全地共享任何子列表以在另一个列表中重用。


小智 5

但如果存在数组和元组的元组,那么元组内的数组是可以修改的。

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

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

>>> a
(['one', 2, 3], (4, 5, 6))
Run Code Online (Sandbox Code Playgroud)

  • 确实不可能存在像集合那样使其内容不可变的东西,因为您需要一种方法来制作任意对象的不可变副本。为此,您必须复制这些对象所属的类,甚至它们引用的内置类。而且,对象可以引用文件系统、网络或其他始终可变的东西。因此,既然我们不能使任意对象不可变,我们就必须满足于可变对象的不可变集合。 (12认同)
  • @JackO'Connor 不完全同意。这完全取决于你如何对世界进行建模:外部可变性始终可以建模为随时间演变的状态,而不是维护单个可变状态 s 我总是可以选择引用不可变的 s_t 。“不可变对象的不可变集合”&lt;--查看 Huskell、Scala 和其他函数式编程语言。在我开始学习Python之前,我曾经从其他人那里听说Python完全支持不变性和fp,但事实证明并非如此。 (2认同)
  • 像 Haskell 这样的语言提供了更多的保证,但如果程序员真的想作恶,他们仍然可以写入“/proc/#/mem”或链接到不安全的库或任何破坏模型的东西。 (2认同)