我对什么是不可变类型感到困惑。我知道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()
同样,对于最后一个类(SortedKeyDict_a),如果我将这种类型的set传递给它:
d = (('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2))
不调用示例方法,它将返回一个字典。带有__new__的SortedKeyDict将其标记为错误。我尝试用__new__将整数传递给RoundFloat类,它没有标记错误。
首先,一个类是否有方法或者它的类结构是什么与可变性无关。
int和float是不可变的。如果我这样做
a = 1
a += 5
它在内存的第一行将名称a指向1。在第二行,它查找1,加5,得到6,然后把a指向内存中的6——它没有以任何方式把1变成6。同样的逻辑适用于下面的例子,使用其他不可变类型:
b = 'some string'
b += 'some other string'
c = ('some', 'tuple')
c += ('some', 'other', 'tuple')
对于可变类型,我可以做一些事情来改变它在内存中存储的值。:
d = [1, 2, 3]
我已经在内存中创建了1、2和3的位置列表。如果我这样做
e = d
我只是把e指向d指向的同一个列表。然后我可以这样做:
e += [4, 5]
e和d所在的列表也会被更新到内存中的位置为4和5。
如果我回到一个不可变类型,用一个元组来做:
f = (1, 2, 3)
g = f
g += (4, 5)
那么f仍然只指向原来的元组——你已经把g指向了一个全新的元组。
现在,用你的例子
class SortedKeyDict(dict):
def __new__(cls, val):
return dict.__new__(cls, val.clear())
你经过的地方
d = (('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2))
(这是一个元组的元组)作为val,你会得到一个错误,因为元组没有.clear()方法-你必须传递dict(d)作为val为它工作,在这种情况下,你会得到一个空的SortedKeyDict作为结果。
你必须理解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语言参考:
双重指控
斑马3套指控模型
如果你是从另一种语言(除了一种非常像Python的语言,比如Ruby)学习Python的,并且坚持用另一种语言来理解它,下面是人们通常会感到困惑的地方:
>>> a = 1
>>> a = 2 # I thought int was immutable, but I just changed it?!
在Python中,赋值不是突变。
在c++中,如果你写a = 2,你是在调用a.operator=(2),这将改变存储在a中的对象(如果a中没有存储对象,这是一个错误)。
在Python中,a = 2对存储在a;它只是意味着2现在存储在a中。(如果a中没有存储对象,也没关系。)
归根结底,这是更深层次区别的一部分。
在c++这样的语言中,变量是内存中的类型化位置。如果a是int型,这意味着它有4个字节,编译器知道它应该被解释为int型。当你令a = 2时,它改变了存储在这4个字节内存中的内容从0,0,0,1变成了0,0,0,2。如果在其他地方有另一个int变量,它有自己的4个字节。
A variable in a language like Python is a name for an object that has a life of its own. There's an object for the number 1, and another object for the number 2. And a isn't 4 bytes of memory that are represented as an int, it's just a name that points at the 1 object. It doesn't make sense for a = 2 to turn the number 1 into the number 2 (that would give any Python programmer way too much power to change the fundamental workings of the universe); what it does instead is just make a forget the 1 object and point at the 2 object instead.
如果赋值不是突变,那么什么是突变呢?
Calling a method that's documented to mutate, like a.append(b). (Note that these methods almost always return None). Immutable types do not have any such methods, mutable types usually do.
Assigning to a part of the object, like a.spam = b or a[0] = b. Immutable types do not allow assignment to attributes or elements, mutable types usually allow one or the other.
Sometimes using augmented assignment, like a += b, sometimes not. Mutable types usually mutate the value; immutable types never do, and give you a copy instead (they calculate a + b, then assign the result to a).
但如果赋值不是突变,那么对象的一部分赋值是如何突变的呢?这就是棘手的地方。a[0] = b不会改变[0](再次,不像c++),但它会改变a(不像c++,除非是间接的)。
这就是为什么最好不要尝试按照您习惯的语言来理解Python的语义,而是根据Python的语义来学习Python的语义。
首先,一个类是否有方法或者它的类结构是什么与可变性无关。
int和float是不可变的。如果我这样做
a = 1
a += 5
它在内存的第一行将名称a指向1。在第二行,它查找1,加5,得到6,然后把a指向内存中的6——它没有以任何方式把1变成6。同样的逻辑适用于下面的例子,使用其他不可变类型:
b = 'some string'
b += 'some other string'
c = ('some', 'tuple')
c += ('some', 'other', 'tuple')
对于可变类型,我可以做一些事情来改变它在内存中存储的值。:
d = [1, 2, 3]
我已经在内存中创建了1、2和3的位置列表。如果我这样做
e = d
我只是把e指向d指向的同一个列表。然后我可以这样做:
e += [4, 5]
e和d所在的列表也会被更新到内存中的位置为4和5。
如果我回到一个不可变类型,用一个元组来做:
f = (1, 2, 3)
g = f
g += (4, 5)
那么f仍然只指向原来的元组——你已经把g指向了一个全新的元组。
现在,用你的例子
class SortedKeyDict(dict):
def __new__(cls, val):
return dict.__new__(cls, val.clear())
你经过的地方
d = (('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2))
(这是一个元组的元组)作为val,你会得到一个错误,因为元组没有.clear()方法-你必须传递dict(d)作为val为它工作,在这种情况下,你会得到一个空的SortedKeyDict作为结果。
Mutable意味着它可以改变/变异。相反,不可改变。
有些Python数据类型是可变的,有些则不是。
让我们来看看哪些类型适合每个类别,并看一些例子。
可变的
在Python中有各种可变类型:
列表
dict
集
让我们看看下面关于列表的例子。
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']
因为list和y都指向同一个列表,我们改变了列表。
不可变的
在一些编程语言中,可以定义一个常量,如下所示
const a = 10
如果调用,它会给出一个错误
a = 20
然而,这在Python中不存在。
然而,在Python中,有各种不可变类型:
没有一个
保龄球
int
浮动
str
元组
让我们看看下面关于字符串的例子。
取字符串a
a = 'abcd'
我们可以得到第一个元素
a[0]
#'a'
如果试图给第一个位置的元素赋一个新值
a[0] = '!'
它会给出一个错误
“str”对象不支持项赋值
当对一个字符串使用+=时,例如
a += 'e'
#'abcde'
它不会给出一个错误,因为它把a指向了一个不同的字符串。
这和下面一样
a = a + 'f'
不改变字符串。
不可变的优点和缺点
•内存中的空间从一开始就知道。它不需要额外的空间。
•通常,它会让事情更有效率。例如,查找字符串的len()要快得多,因为它是字符串对象的一部分。