Python是否有不可变的列表?
假设我想要一个元素有序的集合,但我希望它保持不变,应该如何实现?列表是有序的,但是它们是可变的。
Python是否有不可变的列表?
假设我想要一个元素有序的集合,但我希望它保持不变,应该如何实现?列表是有序的,但是它们是可变的。
是的,它被称为 元组
。
所以,[1,2]
是一个可以被修改的 列表
,而 (1,2)
是一个不可以被修改的 元组
。
更多信息:
一个只有一个元素的 元组
不能写成 (1)
,你需要写成 (1,)
。这是因为解释器对括号有其他用途。
你也可以完全省略括号:1,2
和 (1,2)
是一样的。
请注意,元组
不完全等同于一个不可变的 列表
。点击这里阅读有关 列表和元组之间的差异 的更多信息。
([1,2],3)
),则该元组就不再是真正的不可变,因为列表对象只是对可变对象的指针,虽然指针是不可变的,但所引用的对象并非如此。 - Nisan.H()
。这是唯一需要括号的情况。 - RemcoGerlichfrozenset
? - Garret Wilson随着类型注解和通过mypy
进行类型检查变得更加流行,这个问题值得现代化的回答。
在使用类型注解时,用元组替换List[T]
可能不是理想的解决方案。从概念上讲,列表具有1个通用元数的特性,即它们具有单一的通用参数T
(当然,此参数可以是Union[A, B, C, ...]
,以考虑异构类型列表)。相比之下,元组本质上是多态泛型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)
当然,就运行时行为而言,这并不是不可变的,即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)
在幕后,您仍然可以继续使用常规列表,但类型检查器可以在类型检查时有效地防止任何变异。
同样,您可以通过不可变的数据类来防止修改列表成员(请注意,实际上可以在运行时防止对冻结数据类的字段进行赋值):
@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")
同样的原理也可以通过typing.Mapping
用于字典。
immutable_list
_。如果你的CI强制执行严格的类型检查,这在现今许多项目中都是如此,它就相对安全了。 - bluenote10ImmutableList
的实现。底层列表没有以任何直接数据成员的形式暴露出来,但可以使用成员函数的闭包属性进行访问。如果我们遵循不使用上述属性修改闭包内容的惯例,则此实现将达到预期的目的。这个 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()
你可以使用包含两个元素的元组模拟一个类似Lisp风格的不可变的单链表(注意:这与任意元素元组答案不同,后者创建的元组要不太灵活):
nil = ()
cons = lambda ele, l: (ele, l)
[1, 2, 3]
,你会得到以下内容:l = cons(1, cons(2, cons(3, nil))) # (1, (2, (3, ())))
car
和cdr
函数很简单:car = lambda l: l[0]
cdr = lambda l: l[1]
由于此列表为单向链表,因此在前面添加的时间复杂度为O(1)。由于此列表是不可变的,如果列表中的底层元素也是不可变的,则可以安全地共享任何子列表以在另一个列表中重用。
但是,如果有一个数组和元组的元组,则元组中的数组可以被修改。
>>> a
([1, 2, 3], (4, 5, 6))
>>> a[0][0] = 'one'
>>> a
(['one', 2, 3], (4, 5, 6))
/proc/#/mem
或链接不安全的库或其他任何东西来破坏模型。 - Jack O'Connor列表(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
def foo(array_of_strings):
array_of_strings[0] = "Hello, Jerry"
bar = ['Hi', 'how', 'are', 'you', 'doing']
foo(bar)
print(bar)
输出:
['Hello, Jerry', 'how', 'are', 'you', 'doing']
def foo(array_of_strings):
array_of_strings[0] = "Hello, Jerry"
bar = ['Hi', 'how', 'are', 'you', 'doing']
foo(bar[:])
print(bar)
输出:
['Hi', 'how', 'are', 'you', 'doing']
frozenlist
模块中的FrozenList
类。要安装它,请运行:$ pip install frozenlist
freeze
方法后,列表变得可变,之后对列表的修改会引发RuntimeError
异常。当列表被冻结时,frozenlist
是可哈希的。>>> from frozenlist import FrozenList
>>> fl = FrozenList([1, 2, 3])
>>> fl
<FrozenList(frozen=False, [1, 2, 3])>
>>> fl.append(4)
>>> fl
<FrozenList(frozen=False, [1, 2, 3, 4])>
>>> fl.<b>freeze()</b>
>>> fl.append(5)
Traceback (most recent call last):
File "", line 1, in
File "frozenlist/_frozenlist.pyx", line 106, in frozenlist._frozenlist.FrozenList.append
File "frozenlist/_frozenlist.pyx", line 28, in frozenlist._frozenlist.FrozenList._check_frozen
<b>RuntimeError</b>: Cannot modify frozen list.
>>> hash(fl)
590899387163067792
你可以使用frozenset代替元组。frozenset创建一个不可变的集合。你可以将列表作为frozenset的成员,并使用单个for循环访问frozenset中列表的每个元素。