如何判断一个序列是否可变?

14

对于Python内置类型,list是可变的,但是tuple不可变。对于其他序列,有没有办法判断它们是否可变?就像可变序列通常具有.pop().insert().extend()成员函数一样?所有可变序列和不可变序列是否都继承自单独的内置类型,然后可以用来区分它们?


2
通常情况下,没有编程方法可以告诉我们一个类型(序列或其他)是否可变。要确定这一点,需要阅读该类型的文档并查看其说明。 - BrenBarn
1
你可以尝试使用isinstance(thing, MutableSequence),来自ABCs:https://docs.python.org/3/library/collections.abc.html#collections.abc.MutableSequence - jonrsharpe
8
我觉得你问错了问题。 "Mutable" 和 "immutable" 只是光谱的两端。可以轻松编写一个“mutable”序列,允许添加新元素,但不能删除元素。或者一个只允许添加特定值而不是其他值的序列。你应该问的问题可能是 _“这个序列能否做我需要它做的事情"_,在这种情况下,你可以简单地执行 try: sequence.do_something() except: #don't do anything. - Aran-Fey
1
类似原始类型的类型可能是不可变的。类似容器的类型可能是可变的。 - R.A.Munna
5
为什么你一开始需要这个呢?请提供使用案例示例。 - Azat Ibrakov
显示剩余2条评论
4个回答

17
你可以检查类型是否是collections.abc.MutableSequence抽象基类的子类(或Python 2中的collections.MutableSequence):
>>> issubclass(list, MutableSequence)
True
>>> issubclass(tuple, MutableSequence)
False

>>> isinstance([], MutableSequence)
True
>>> isinstance((), MutableSequence)
False

请注意,与某些ABCs(例如提供issubclass/isinstance挂钩的CollectionIterable)不同,这个类要求其子类必须显式注册,因此对于所有类似序列的类型,这可能无法直接使用。
但是,只要实现了所需的抽象方法,您可以使用MutableSequence.register(MyType)手动将类型注册为子类。

4
这个解决方案似乎有一个缺陷,它可能无法与用户定义的可变序列一起使用...尽管可以承认这不是常见情况... - Alex L
@AlexL 公平地说,一个类能够在没有设计者同意的情况下自行声明为另一种类型的父类的想法是相当疯狂的。当类没有定义任何其他方法时,例如 Iterable,它实际上只是一个接口,规定子类定义了 __iter__。但对于定义了混合方法的 ABC(如 MutableSequence),强制将其添加到其他类中而没有选择退出将是危险的。我想这就是为什么它必须是明确的设计决策的原因。 - Will Vousden
@AlexL 记住:即使类设计者忘记在 ABC 中注册它,你仍然可以在自己的代码中注册它 :-) - Will Vousden
1
当您实现自定义(Mutable)序列时,明确继承ABC是有意义的:它为您提供了有用的mixin,节省了一些实现时间(有时以运行时成本为代价,取决于mixin方法和强制方法的实现方式,但并不总是如此)。 - Jonas Schäfer
是的,同意所有。虽然最有可能的情况是在运行时无法知道该事物是否可变...在这种情况下,注册并不是一个真正的选择... - Alex L
有人可能会认为,如果自定义序列没有继承ABC,则不希望将其标识为这样的序列。也许使用代码并不依赖于它,因为它可能不想在未来的版本中承诺它。使用isinstance进行检查是与此一致的,因此如果没有受到第三方或遗留代码的限制,我会选择该方法。 - spectras

2

在不了解您试图实现什么之前,就没有简单的解决方案,因此我们需要了解可变性是什么?

让我们简单地定义可变类型为这种类型的实例,我们可以在某个位置设置项(在字典中按键,在列表中按索引),即它们实现__setitem__方法。

Python中检查某些内容的最常用方法是请求宽恕而非允许,因此像这样做会有所帮助:

def is_mutable(cls):
    try:
        cls.__setitem__
    except AttributeError:
        return False
    else:
        return True

但它也可以被替换为

def is_mutable(cls):
    return hasattr(cls, '__setitem__')

两者的工作方式相似,取决于你的喜好。

示例

types = [tuple, str, list, dict]
for type_ in types:
    print(type_.__name__, 'is mutable:', is_mutable(type_))

给我们
tuple is mutable: False
str is mutable: False
list is mutable: True
dict is mutable: True

2
请注意,set没有__setitem__但它是可变的。另一方面,set也不是序列类型,但dict也不是。 - tobias_k
是的,根据这个定义,“set”是不可变的。 - Azat Ibrakov
2
set 绝对是可变的,但它不是“可变序列”——dict 也不是,因此 __setitem__ 的存在可能不是最好的指标。此外,还有其他可变序列,例如(严格的)栈和队列,它们不允许随机访问。 - tobias_k
无法确定随机类型是否可变,但这种方法适用于tuple / list / str,发帖者应该决定是否认为dict是可变的。 - Azat Ibrakov
如果我们尝试为 set 添加支持,我们可能还想检查标准库中的所有类型,但仍然无法完美地处理所有类型。 - Azat Ibrakov

0
如果你的序列被命名为seq,测试不可变性的“鸭子类型”方式是尝试给seq[0]赋值,并在它不起作用时捕获异常...


Python 的标准可变序列定义还要求实现 del seq[0]seq.insert - donkopotamus
仅凭表面价值看,这个建议也会失去该索引处的现有值。否则,这是一个不错的方法。你能提出一个通用测试,而实际上并不改变序列吗? - kojiro
2
@kojiro> 没有通用解决方案,因为该值可能是只写或具有副作用,因此一旦分配,您无法撤消更改。诚然,这种情况很少见,但最好不要使用通用解决方案。 - spectras
1
将值分配给“seq [0]”也适用于字典,但不适用于空序列。 - Jonas Schäfer
1
@kojiro 实际上可以通过 seq[0] = seq[0] 避免修改序列,但这仍然无法解决空或字典边缘情况。 - Jonas Schäfer
(从问题的陈述中,我假设我们已经知道它是一个序列,而不是字典...) - Alex L

-5

如果一个对象只包含不可变类型的子对象,则该对象是不可变的。 如果一个类型是内置的不可变类型:str、int、bool、float、tuple,则它是不可变的。


这是不正确的。仅包含不可变值的list仍然是可变的 --- 您可以用其他值替换其中任何不可变值。(您也可以添加或删除值。) - Kevin J. Chase

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接