当给另一个变量赋值时,它们指向同一对象,因此,如何为其中一个变量赋值,但变量仍指向同一对象!
a = 10
b = a
a -= 1
print(b) #expect to print 9 but it print 10
如何在不更改id的情况下在python中重新分配变量?
答案 0 :(得分:8)
我不确定您是否对Python中的变量或不可变值感到困惑。因此,我将对两者进行解释,答案的一半可能看起来像“不,我已经知道了”,但是另一半应该是有用的。
在Python中(与C不同),变量不是值所在的位置。这只是一个名字。这些值可以放在它们想要的任何位置。 1 因此,当您执行此操作时:
a = 10
b = a
您没有将b
引用为a
。这个想法在Python中甚至没有意义。您将a
命名为10
,然后将b
命名为10
。如果以后再执行此操作:
a = 11
…您已经将a
用作11
的名称,但这对b
没有影响-它仍然只是10
的名称。
这还意味着id(a)
不会为您提供变量a
的ID,因为没有 这样的东西。 a
只是在某个命名空间中查找的名称(例如,模块的全局变量)。是具有ID的 value ,11
(或者,如果您早先运行过,则为不同的值10
)。 (虽然我们正在这样做:它也是键入的值,而不是变量。此处不相关,但值得了解。)
关于可变性,事情变得有些棘手。例如:
a = [1, 2, 3]
b = a
这仍然使a
和b
都成为列表的名称。
a[0] = 0
这没有分配给a
,因此a
和b
仍然是同一列表的名称。它确实分配给该列表的一部分a[0]
。因此,a
和b
都命名的列表现在包含[0, 2, 3]
。
a.extend([4, 5])
这显然具有相同的作用:a
和b
现在将列表命名为[0, 2, 3, 4, 5]
。
这是令人困惑的地方:
a += [6]
是重新绑定a
的赋值,还是只是变异了a
为其命名的值?实际上,两者都是。隐藏的意思是:
a = a.__iadd__([6])
…或大致:
_tmp = a
_tmp.extend([6])
a = _tmp
因此,我们 分配给a
,但我们正在为其分配与已经命名的相同的值。同时,我们也正在对该值进行突变,该值仍然是b
命名的值。
所以现在:
a = 10
b = 10
a += 1
您可能会猜到最后一行是这样的:
a = a.__iadd__(1)
那不是很正确,因为a
没有定义__iadd__
方法,因此它可以归结为:
a = a.__add__(1)
但这不是重要的部分。 2 重要的部分是,因为整数与列表不同,是不可变的。您无法像在INTERCAL或Fortran(或类似的Fortran)中那样将数字10转换为数字11,或者您曾经是最奇怪的X-Man的那个奇怪的梦想。而且没有可以设置为11的“保持数字10的变量”,因为它不是C ++。因此,该必须返回一个新值,即值11
。
因此,a
成为该新11
的名称。同时,b
仍然是10
的名称。就像第一个例子一样。
但是,在所有这些告诉你要做想要的事情是多么不可能之后,我将告诉你要做想要的事情是多么容易。
还记得以前,当我提到您可以对列表进行突变时,该列表的所有名称都会看到新值吗?所以,如果您这样做:
a = [10]
b = a
a[0] += 1
现在b[0]
将是11
。
或者您可以创建一个类:
class Num:
pass
a = Num()
a.num = 10
b = a
a.num += 1
现在,b.num
是11
。
或者您甚至可以创建一个实现__add__
和__iadd__
和all the other numeric methods的类,以便它可以(几乎)透明地保存数字,但是却是可变的。
class Num:
def __init__(self, num):
self.num = num
def __repr__(self):
return f'{type(self).__name__}({self.num})'
def __str__(self):
return str(self.num)
def __add__(self, other):
return type(self)(self.num + other)
def __radd__(self, other):
return type(self)(other + self.num)
def __iadd__(self, other):
self.num += other
return self
# etc.
现在:
a = Num(10)
b = a
a += 1
b
是与Num(11)
相同的a
的名称。
但是,如果您确实要执行此操作,则应考虑制作Integer
之类的特定内容,而不是保留可充当数字的任何内容的通用Num
之类的内容,并在其中使用适当的ABC numbers
模块,以验证您涵盖了所有关键方法,免费获得了许多可选方法的实现以及能够通过isinstance
类型检查。 (并且可能以其num.__int__
的方式在其构造函数中调用int
,或者至少是特殊情况的isinstance(num, Integer)
,因此您最终不会以对引用的引用作为引用……除非那就是你想要的。)
1。好吧,他们住在口译员希望他们居住的任何地方,例如Ceaușescu统治下的罗马尼亚人。但是,如果您是用C编写的内置/扩展类型并且是Party的付费成员,则可以使用不依赖于__new__
进行分配的构造函数来覆盖super
你别无选择。
2。但这并不是完全不重要的。按照约定(当然,所有内置和stdlib类型都遵循约定),__add__
不会突变,__iadd__
不会突变。因此,像list
这样的可变类型会同时定义这两种类型,这意味着它们在a += b
上具有就地行为,但在a + b
上具有复制行为,而tuple
和{{1} }仅定义int
,因此它们都具有复制行为。 Python不会强迫您以这种方式执行操作,但是如果您不选择这两种类型之一,您的类型将非常奇怪。如果您熟悉C ++,则是一样的-通常通过就地变异并返回对__add__
的引用来实现operator+=
,并通过复制然后返回{{1 }}上,但是该语言不会强迫您这样做,否则您会感到困惑。
答案 1 :(得分:0)
变量没有ID,ID属于对象。 整数,例如:10,9是分开的和不可变对象,'a','b'变量仅是引用。 id(a)确实给出了所引用对象的ID。对象'10'不能在适当位置变异。
id(10), id(9)
Out[16]: (10943296, 10943264)
In [17]: a,b=10,10
In [18]: id(a),id(b)
Out[18]: (10943296, 10943296)
In [19]: b=a-1
In [20]: id(b)==id(9)
Out[20]: True
但是,列表是可变对象,可以使用[:]运算符(当然还有其方法,例如:append,extend)在适当的位置进行更改:
l=[10,5]
In [29]: id(l),id(l[0]),id(l[1])
Out[29]: (139638954619720, 10943296, 10943136)
In [30]: id(l[0])==id(10)
Out[30]: True
In [31]: l=[10,5] # a new list object created, though the content is the same
In [32]: id(l)
Out[32]: 139638920490248
In [33]: l[:]=["a","b"] # in place changes
In [34]: id(l)
Out[34]: 139638920490248