繁体   English   中英

不可变与可变类型

[英]Immutable vs Mutable types

我对什么是不可变类型感到困惑。 我知道float对象被认为是不可变的,我的书中有这种类型的例子:

class RoundFloat(float):
    def __new__(cls, val):
        return float.__new__(cls, round(val, 2))

由于类结构/层次结构,这是否被认为是不可变的?这意味着float位于类的顶部并且是它自己的方法调用。 类似于这种类型的示例(即使我的书说dict是可变的):

class SortedKeyDict(dict):
    def __new__(cls, val):
        return dict.__new__(cls, val.clear())

而可变的东西在类中有方法,用这种类型的例子:

class SortedKeyDict_a(dict):
    def example(self):
        return self.keys()

另外,对于最后一个class(SortedKeyDict_a) ,如果我将这种类型的集合传递给它:

d = (('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2))

不调用example方法,它返回一个字典。 带有__new__SortedKeyDict__new__标记为错误。 我尝试使用__new__将整数传递给RoundFloat类,并且没有标记错误。

什么? 浮点数是不可变的吗? 但我做不到

x = 5.0
x += 7.0
print x # 12.0

那不是“mut”x吗?

好吧,您同意字符串是不可变的,对吗? 但是你可以做同样的事情。

s = 'foo'
s += 'bar'
print s # foobar

变量的值会发生变化,但它会通过更改变量所指的内容而发生变化。 可变类型可以以这种方式改变,它也可以“就地”改变。

这是区别。

x = something # immutable type
print x
func(x)
print x # prints the same thing

x = something # mutable type
print x
func(x)
print x # might print something different

x = something # immutable type
y = x
print x
# some statement that operates on y
print x # prints the same thing

x = something # mutable type
y = x
print x
# some statement that operates on y
print x # might print something different

具体例子

x = 'foo'
y = x
print x # foo
y += 'bar'
print x # foo

x = [1, 2, 3]
y = x
print x # [1, 2, 3]
y += [3, 2, 1]
print x # [1, 2, 3, 3, 2, 1]

def func(val):
    val += 'bar'

x = 'foo'
print x # foo
func(x)
print x # foo

def func(val):
    val += [3, 2, 1]

x = [1, 2, 3]
print x # [1, 2, 3]
func(x)
print x # [1, 2, 3, 3, 2, 1]

您必须了解 Python 将其所有数据表示为对象。 其中一些对象(如列表和字典)是可变的,这意味着您可以更改其内容而无需更改其身份。 其他对象如整数、浮点数、字符串和元组是无法更改的对象。 理解这一点的一种简单方法是查看对象 ID。

下面你会看到一个不可变的字符串。 你不能改变它的内容。 如果您尝试更改它,它将引发TypeError 此外,如果我们分配新内容,则会创建一个新对象,而不是修改内容。

>>> s = "abc"
>>> id(s)
4702124
>>> s[0] 
'a'
>>> s[0] = "o"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>> s = "xyz"
>>> id(s)
4800100
>>> s += "uvw"
>>> id(s)
4800500

你可以用一个列表来做到这一点,它不会改变对象的身份

>>> i = [1,2,3]
>>> id(i)
2146718700
>>> i[0] 
1
>>> i[0] = 7
>>> id(i)
2146718700

要阅读有关 Python 数据模型的更多信息,您可以查看 Python 语言参考:

常见的不可变类型:

  1. 数字: int()float()complex()
  2. 不可变序列: str()tuple()frozenset()bytes()

常见的可变类型(几乎所有其他类型):

  1. 可变序列: list() , bytearray()
  2. 设置类型: set()
  3. 映射类型: dict()
  4. 类,类实例
  5. 等等。

快速测试类型是否可变的一个技巧是使用id()内置函数。

示例,使用整数,

>>> i = 1
>>> id(i)
***704
>>> i += 1
>>> i
2
>>> id(i)
***736 (different from ***704)

使用清单,

>>> a = [1]
>>> id(a)
***416
>>> a.append(2)
>>> a
[1, 2]
>>> id(a)
***416 (same with the above id)

首先,一个类是否有方法或它的类结构是什么与可变性无关。

intfloat不可变的 如果我做

a = 1
a += 5

它指向的名字a1某处内存在第一行。 在第二行,它查找1 ,加上5 ,得到6 ,然后将a指向内存中的6 —— 它没有以任何方式将1更改6 相同的逻辑适用于以下示例,使用其他不可变类型:

b = 'some string'
b += 'some other string'
c = ('some', 'tuple')
c += ('some', 'other', 'tuple')

对于可变类型,我可以做一些实际更改它存储在 memory 中的值的事情。 和:

d = [1, 2, 3]

我在内存中创建了123的位置列表。 如果我那么做

e = d

我只是将e指向同一个list d指向。 然后我可以这样做:

e += [4, 5]

并且ed指向的列表将被更新为在内存中也有45的位置。

如果我回到不可变类型并使用tuple执行此操作:

f = (1, 2, 3)
g = f
g += (4, 5)

然后f仍然只指向原始tuple - 你已经将g指向了一个全新的tuple

现在,以你的例子

class SortedKeyDict(dict):
    def __new__(cls, val):
        return dict.__new__(cls, val.clear())

你经过的地方

d = (('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2))

(这是一个tupletuples )作为val ,你得到一个错误,因为tuple没有.clear()方法——你必须将dict(d)作为val传递才能工作,在在这种情况下,您将得到一个空的SortedKeyDict结果。

可变对象和不可变对象的区别

定义

可变对象:创建后可以更改的对象。
不可变对象:创建后无法更改的对象。

在 python 中,如果更改不可变对象的值,它将创建一个新对象。

可变对象

以下是 Python 中可变类型的对象:

  1. list
  2. Dictionary
  3. Set
  4. bytearray
  5. user defined classes

不可变对象

以下是 Python 中不可变类型的对象:

  1. int
  2. float
  3. decimal
  4. complex
  5. bool
  6. string
  7. tuple
  8. range
  9. frozenset
  10. bytes

一些悬而未决的问题

问题字符串是不可变类型吗?
的,但你能解释一下吗:证明 1

a = "Hello"
a +=" World"
print a

输出

"Hello World"

在上面的示例中,字符串曾经被创建为“Hello”,然后更改为“Hello World”。 这意味着该字符串是可变类型的。 但是当我们检查它的身份以查看它是否是可变类型时,它不是。

a = "Hello"
identity_a = id(a)
a += " World"
new_identity_a = id(a)
if identity_a != new_identity_a:
    print "String is Immutable"

输出

String is Immutable

证明2

a = "Hello World"
a[0] = "M"

输出

TypeError 'str' object does not support item assignment

问题元组是不可变类型吗?
回答是的,是的。 证明1

tuple_a = (1,)
tuple_a[0] = (2,)
print a

输出

'tuple' object does not support item assignment

如果您是从另一种语言(除了与 Python 非常相似的语言,如 Ruby)接触 Python,并坚持使用另一种语言来理解它,那么人们通常会感到困惑:

>>> a = 1
>>> a = 2 # I thought int was immutable, but I just changed it?!

在 Python 中,赋值不是 Python 中的变异。

在 C++ 中,如果你写a = 2 ,你就是在调用a.operator=(2) ,它会改变存储在a的对象。 (如果没有存储在对象a ,这是一个错误。)

在 Python 中, a = 2对存储在a中的内容没有任何作用; 它只是意味着2现在存储在a (如果没有存储在对象a ,那很好。)


归根结底,这是更深层次区别的一部分。

像 C++ 这样的语言中的变量是内存中的类型化位置。 如果aint ,则意味着它是编译器知道应该被解释为int的 4 个字节。 因此,当您执行a = 2 ,它会将这 4 个字节的内存中存储的内容从0, 0, 0, 1更改为0, 0, 0, 2 如果其他地方有另一个 int 变量,它有自己的 4 个字节。

像 Python 这样的语言中的变量是具有自己生命周期的对象的名称。 数字1有一个对象,数字2另一个对象。 并且a不是表示为int 4 个字节的内存,它只是一个指向1对象的名称。 a = 2将数字 1 变成数字 2 是没有意义的(这会给任何 Python 程序员太多的权力来改变宇宙的基本运作); 它的作用,而不是仅仅做a忘记了1在对象和点2的对象来代替。


那么,如果赋值不是突变,那么什么突变呢?

  • 调用一个记录在案的方法来改变,比如a.append(b) (请注意,这些方法几乎总是返回None )。 不可变类型没有任何这样的方法,可变类型通常有。
  • 分配给对象的一部分,如a.spam = ba[0] = b 不可变类型不允许分配给属性或元素,可变类型通常允许其中之一。
  • 有时使用增广赋值,如a += b ,有时不使用。 可变类型通常会改变值; 不可变类型从不这样做,而是给你一个副本(他们计算a + b ,然后将结果分配给a )。

但是如果赋值不是变异,那么如何赋值给对象变异的一部分呢? 这就是它变得棘手的地方。 a[0] = b发生变异a[0]再次,与C ++),但它确实发生变异a (不像C ++,除了间接地)。

所有这一切就是为什么最好不要尝试将 Python 的语义置于您习惯的语言的角度,而是根据 Python 的语义学习它们自己的术语。

一个对象是否可变取决于它的类型。 这不取决于它是否具有某些方法,也不取决于类层次结构的结构。

用户定义的类型(即类)通常是可变的。 有一些例外,例如不可变类型的简单子类。 其他不可变类型包括一些内置类型,例如intfloattuplestr ,以及一些用 C 实现的 Python 类。

Python 语言参考中“数据模型”一章的一般解释:

某些对象的值可能会发生变化。 值可以改变的对象被称为可变的; 其值一旦创建就不可更改的对象称为不可变的。

(包含对可变对象的引用的不可变容器对象的值可以在后者的值改变时改变;但是容器仍然被认为是不可变的,因为它包含的对象集合不能改变。因此,不可变性并不是严格意义上的与具有不可更改的值相同,它更微妙。)

一个对象的可变性是由它的类型决定的; 例如,数字、字符串和元组是不可变的,而字典和列表是可变的。

一个可变对象必须至少有一个能够改变对象的方法。 例如, list对象有append方法,它实际上会改变对象:

>>> a = [1,2,3]
>>> a.append('hello') # `a` has mutated but is still the same object
>>> a
[1, 2, 3, 'hello']

但是float类没有改变 float 对象的方法。 你可以做:

>>> b = 5.0 
>>> b = b + 0.1
>>> b
5.1

=操作数不是方法。 它只是在变量和它右边的任何东西之间进行绑定,没有别的。 它永远不会改变或创建对象。 从现在开始,它是变量将指向的内容的声明。

当你做b = b + 0.1=操作数将变量绑定到一个新的浮点数,这是用5 + 0.1 te 结果创建的。

当您将变量分配给现有对象时,无论是否可变, =操作数都会将该变量绑定到该对象。 没有更多的事情发生

在任何一种情况下, =只是进行绑定。 它不会更改或创建对象。

当您执行a = 1.0=操作数不是创建浮点数,而是该行的1.0部分。 实际上,当您编写1.0它是float(1.0)构造函数调用返回浮点对象的简写。 (这就是为什么如果您键入1.0并按回车键,您会得到下面打印的“echo” 1.0 ;这是您调用的构造函数的返回值)

现在,如果b是浮点数并且您分配a = b ,则两个变量都指向同一个对象,但实际上变量无法相互通信,因为该对象是不可变的,如果您执行b += 1 ,现在b指向一个新对象,而a仍然指向旧对象,无法知道b指向什么。

但如果c是,比方说,一个list ,并分配a = c ,现在ac可以“comunicate”,因为list是可变的,如果你这样做c.append('msg')然后只检查a你得到消息。

(顺便说一下,每个对象都有一个唯一的 id 编号,您可以通过id(x) 。因此您可以检查对象是否相同或不检查其唯一 id 是否已更改。)

如果该类的每个对象在实例化时具有固定值且不能随后更改,则该类是不可变的

换句话说,更改该变量(name)的整个值或不理会它。

例子:

my_string = "Hello world" 
my_string[0] = "h"
print my_string 

您希望它可以工作并打印hello world但这会引发以下错误:

Traceback (most recent call last):
File "test.py", line 4, in <module>
my_string[0] = "h"
TypeError: 'str' object does not support item assignment

解释器说:我不能改变这个字符串的第一个字符

您必须更改整个string才能使其正常工作:

my_string = "Hello World" 
my_string = "hello world"
print my_string #hello world

检查此表:

在此处输入图片说明

来源

在我看来,您正在与 mutable/immutable 实际上意味着什么这个问题作斗争 所以这里有一个简单的解释:

首先,我们需要一个基础来进行解释。

所以把你编程的任何东西想象成一个虚拟对象,一些作为二进制数序列保存在计算机内存中的东西。 (不过不要想得太难。^^)现在在大多数计算机语言中,您不会直接使用这些二进制数,而是更多地使用二进制数的解释。

例如,您不会考虑像 0x110、0xaf0278297319 或类似的数字,而是考虑像 6 这样的数字或像“Hello, world”这样的字符串。 无论如何,这些数字或字符串都是对计算机内存中二进制数的解释。 对于变量的任何值也是如此。

简而言之:我们使用实际值编程,而是使用实际二进制值的解释。

现在我们确实有出于逻辑和其他“整洁的东西”而不能改变的解释,而有些解释很可能会改变。 例如,想想模拟一个城市,换句话说,一个有许多虚拟对象的程序,其中一些是房屋。 现在这些虚拟对象(房子)可以被改变,它们仍然可以被认为是相同的房子吗? 他们当然可以。 因此它们是可变的:它们可以被改变而不会变成“完全”不同的对象。

现在想想整数:它们也是虚拟对象(计算机内存中的二进制数序列)。 因此,如果我们更改其中之一,例如将值 6 加一,它仍然是 6 吗? 当然不是。 因此任何整数都是不可变的。

所以:如果一个虚拟对象的任何变化意味着它实际上变成了另一个虚拟对象,那么它被称为不可变的。

最后说明:

(1) 永远不要将可变和不可变的真实体验与使用某种语言的编程混淆:

每种编程语言都有自己的定义,哪些对象可以静音,哪些可以不静音。

因此,虽然您现在可能理解了含义上的差异,但您仍然需要了解每种编程语言的实际实现。 ...确实可能有一种语言的目的是将 6 静音变成 7。然后,这将是一些非常疯狂或有趣的东西,例如平行宇宙的模拟。^^

(2) 这个解释当然不科学,是为了帮助你掌握可变和不可变的区别。

这个答案的目标是创建一个地方来找到所有关于如何判断您是否正在处理变异/非变异(不可变/可变)的好主意,以及在可能的情况下如何处理? 有时突变是不可取的,python 在这方面的行为对于从其他语言进入它的编码人员来说可能会违反直觉。

根据@mina-gabriel 的有用帖子:

分析上述内容并结合@arrakën 的帖子:

什么不能意外改变?

  • 标量(存储单个值的变量类型)不会意外更改
    • 数字示例:int()、float()、complex()
  • 有一些“可变序列”:
    • str()、tuple()、frozenset()、bytes()

什么可以?

  • 类似对象的列表(列表、字典、集合、bytearray())
  • 这里的一篇文章也说类和类实例,但这可能取决于类继承自什么和/或它是如何构建的。

“意外”我的意思是来自其他语言的程序员可能不会期望这种行为(除了 Ruby 或其他一些“类似 Python”的语言)。

添加到这个讨论:

这种行为是一个优势,因为它可以防止您意外地将占用内存的大型数据结构的多个副本填充到您的代码中。 但是,如果这是不受欢迎的,我们如何解决它?

对于列表,简单的解决方案是像这样构建一个新的:

列表 2 = 列表(列表 1)

使用其他结构......解决方案可能会更棘手。 一种方法是遍历元素并将它们添加到一个新的空数据结构(相同类型)。

当您传入可变结构时,函数可以改变原始函数。 怎么讲?

  • 对该线程上的其他评论进行了一些测试,但有评论表明这些测试不是完全证明
  • object.function() 是原始对象的一种方法,但只有其中一些会发生变化。 如果他们什么都不返回,他们可能会这样做。 人们会期望 .append() 在没有对其名称进行测试的情况下发生变异。 .union() 返回 set1.union(set2) 的并集并且不会发生变异。 如有疑问,可以检查该函数的返回值。 如果 return = None,则不会发生变异。
  • sorted() 在某些情况下可能是一种解决方法。 由于它返回原始版本的排序版本,因此它可以让您在以其他方式开始处理原始版本之前存储未变异的副本。 但是,此选项假定您不关心原始元素的顺序(如果您这样做,则需要找到另一种方式)。 相比之下, .sort() 改变了原始的(正如人们所期望的)。

非标准方法(如果有帮助):在 MIT 许可下发布的 github 上找到了这个:

  • github 存储库下: tobgu 命名: pyrsistent
  • 它是什么:Python 持久数据结构代码编写用于在不希望发生突变时代替核心数据结构

对于自定义类,@semicolon 建议检查是否有__hash__函数,因为可变对象通常不应该有__hash__()函数。

这就是我目前在这个主题上积累的全部内容。 欢迎其他想法、更正等。 谢谢。

一种思考差异的方式:

在python中对不可变对象的赋值可以被认为是深拷贝,而对可变对象的赋值是浅的

最简单的答案:

可变变量是其值可以就地更改的变量,而在不可变变量中,值的更改不会就地发生。 修改不可变变量将重建相同的变量。

例子:

>>>x = 5

将创建一个由 x 引用的值 5

x -> 5

>>>y = x

此语句将使 y 指代 x 中的 5

x -------------> 5 <-----------y

>>>x = x + y

因为 x 是一个整数(不可变类型)已经被重建。

在语句中,RHS 上的表达式将导致值 10,当它分配给 LHS (x) 时,x 将重建为 10。所以现在

x--------->10

y--------->5

可变意味着它可以改变/变异 不可变的相反。

有些 Python 数据类型是可变的,有些则不是。

让我们找出适合每个类别的类型并查看一些示例。


可变的

在 Python 中有各种可变类型:

  • 列表

  • 字典

让我们看看下面的lists示例。

list = [1, 2, 3, 4, 5]

如果我执行以下操作来更改第一个元素

list[0] = '!'
#['!', '2', '3', '4', '5']

它工作得很好,因为列表是可变的。

如果我们考虑该列表,该列表已更改,并为其分配一个变量

y = list

如果我们更改列表中的一个元素,例如

list[0] = 'Hello'
#['Hello', '2', '3', '4', '5']

如果一个打印y它会给

['Hello', '2', '3', '4', '5']

由于listy指的是同一个列表,我们已经更改了列表。


不可变

在某些编程语言中,可以定义一个常量,如下所示

const a = 10

如果有人打电话,它会给出一个错误

a = 20

但是,这在 Python 中不存在。

然而,在 Python 中,有多种不可变类型:

  • 没有任何

  • 布尔值

  • 整数

  • 漂浮

  • 字符串

  • 元组

让我们看看下面的strings示例。

取字符串a

a = 'abcd'

我们可以得到第一个元素

a[0]
#'a'

如果试图为第一个位置的元素分配一个新值

a[0] = '!'

它会报错

“str”对象不支持项目分配

当对字符串说 += 时,例如

a += 'e'
#'abcde'

它不给一个错误,因为它指向a以不同的字符串。

这将与以下相同

a = a + 'f'

并且不改变字符串。

不可变的一些优点和缺点

• 从一开始就知道内存中的空间。 它不需要额外的空间。

• 通常,它使事情更有效率。 例如,查找字符串的len()会快得多,因为它是字符串对象的一部分。

我还没有阅读所有答案,但所选答案不正确,我认为作者有一个想法,即能够重新分配变量意味着任何数据类型都是可变的。 事实并非如此。 可变性与按引用传递而不是按值传递有关。

假设您创建了一个列表

a = [1,2]

如果你要说:

b = a
b[1] = 3

即使您在 B 上重新分配了一个值,它也会重新分配 a 上的值。 这是因为当您分配“b = a”时。 您将“引用”传递给对象而不是值的副本。 字符串、浮点数等不是这种情况。这使得列表、字典等可变,但布尔值、浮点数等不可变。

在 Python 中,有一种简单的方法可以了解:

不可变:

    >>> s='asd'
    >>> s is 'asd'
    True
    >>> s=None
    >>> s is None
    True
    >>> s=123
    >>> s is 123
    True

可变:

>>> s={}
>>> s is {}
False
>>> {} is {}
Flase
>>> s=[1,2]
>>> s is [1,2]
False
>>> s=(1,2)
>>> s is (1,2)
False

和:

>>> s=abs
>>> s is abs
True

所以我认为内置函数在 Python 中也是不可变的。

但我真的不明白浮动是如何工作的:

>>> s=12.3
>>> s is 12.3
False
>>> 12.3 is 12.3
True
>>> s == 12.3
True
>>> id(12.3)
140241478380112
>>> id(s)
140241478380256
>>> s=12.3
>>> id(s)
140241478380112
>>> id(12.3)
140241478380256
>>> id(12.3)
140241478380256

这太奇怪了。

例如,对于不可变对象,赋值会创建值的新副本。

x=7
y=x
print(x,y)
x=10 # so for immutable objects this creates a new copy so that it doesnot 
#effect the value of y
print(x,y)

对于可变对象,赋值不会创建值的另一个副本。 例如,

x=[1,2,3,4]
print(x)
y=x #for immutable objects assignment doesn't create new copy 
x[2]=5
print(x,y) # both x&y holds the same list

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM