将变量分配给另一个变量时,它们指向同一对象。那么,如何在变量仍指向同一对象的情况下更改其中之一的值?
a = 10 b = a a -= 1 print(b) #expect to print 9 but it print 10
如何在python中重新分配变量而不更改其ID?
我不确定您是否对Python中的变量或不可变值感到困惑。因此,我将对两者进行解释,答案的一半可能看起来像“不,我已经知道了”,但另一半应该是有用的。
在Python中(与C不同),变量不是值所在的位置。这只是一个名字。价值观生活在他们想要的任何地方。1因此,当您执行此操作时:
a = 10 b = a
您没有b提及a。这个想法在Python中甚至没有意义。您正在a为命名10,然后b为命名10。如果以后再执行此操作:
b
a
10
a = 11
…您已经a为命名11,但这对并没有影响b-它仍然只是的名字10。
11
这也意味着,id(a)是不是给你的变量的ID a,因为那里 是 没有这样的事情。a只是在某个名称空间中查找的名称(例如,模块的全局变量)。它的 价值 ,11(或者,如果你运行它更早的版本,不同的值10),有一个ID。(虽然我们正在这样做:它也是键入的值,而不是变量。在这里不相关,但值得了解。)
id(a)
关于可变性,事情变得有些棘手。例如:
a = [1, 2, 3] b = a
这仍然使a和b两个名称的列表。
a[0] = 0
这不分配a,所以a并b仍然是相同的列表名称。它 确实 分配给a[0],这是该列表的一部分。因此,列表a和b两个名字现在持有[0, 2, 3]。
a[0]
[0, 2, 3]
a.extend([4, 5])
这显然做同样的事情:a和b现在的名字列表[0, 2, 3, 4, 5]。
[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__方法,所以可以归结为:
__iadd__
a = a.__add__(1)
但这不是重要的部分。2重要的一点是,因为整数与列表不同,是不可变的。您无法像在INTERCAL或Fortran(或类似的Fortran)中那样将数字10转换为11,或者您曾经是最奇怪的X- Man的那个奇怪的梦想。而且没有可以设置为11的“保持数字10的变量”,因为它不是C ++。因此,这 必须 返回一个新值,即value 11。
因此,a成为该新产品的名称11。同时,b仍然是的名称10。就像第一个例子一样。
但是,在所有这些告诉您要做您想做的事情是多么不可能之后,我将告诉您做您想做的事情是多么容易。
还记得以前,当我提到您可以对列表进行突变时,该列表的所有名称都会看到新值吗?因此,如果您这样做:
a = [10] b = a a[0] += 1
现在b[0]将会是11。
b[0]
或者您可以创建一个类:
class Num: pass a = Num() a.num = 10 b = a a.num += 1
现在b.num是11。
b.num
或者你甚至可以创建一个类,工具__add__和__iadd__和所有其他数字方法,所以它可以容纳数(几乎)透明的,但这样做的性情不定地。
__add__
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。
Num(11)
但是,如果您确实想执行此操作,则应考虑制作特定的东西(Integer而不是泛型的Num东西)来保存像数字一样的内容,并在numbers模块中使用适当的ABC来验证您是否涵盖了所有关键方法,从而获得大量可选方法的免费实现,并能够通过isinstance类型检查。(并且可能num.__int__以这种方式调用它的构造函数int,或者至少是特殊情况,isinstance(num, Integer)这样您就不会最终得到对引用的引用……除非您要这样。)
Integer
Num
numbers
isinstance
num.__int__
int
isinstance(num, Integer)
好吧,他们住在口译员想要他们住的地方,就像在齐奥塞斯库统治下的罗马尼亚人一样。但是,如果您是用C编写的内置/扩展类型并且是Party的付费成员,则可以__new__使用不依赖于super分配的构造函数来覆盖它,否则您别无选择。
__new__
super
2.但这并不是完全不重要的。按照约定(当然,在所有内置类型和stdlib类型中都遵循约定),__add__不进行更改,而是进行更改__iadd__。因此,像这样的可变类型list定义了两者,这意味着它们对都具有就地行为,a += b但对却具有复制行为a + b,而不可变类型像对tuple和都int定义了__add__,因此它们对两者都有复制行为。Python不会强迫您以这种方式执行操作,但是如果您的类型没有选择这两种类型之一,您的类型将非常奇怪。如果您熟悉C ++,则是一样的- 通常operator+=通过就地变异并返回的引用this,然后operator+复制然后返回来实现+=在副本上,但是语言不会强迫您这样做,如果不这样做,只会令人感到困惑。
list
a += b
a + b
tuple
operator+=
this
operator+
+=