sar*_* Kh 4 python variable-assignment
当一个变量分配给另一个变量时,它们指向同一个对象,那么,如何对其中一个变量进行值变换,但变量仍然指向同一个对象!
a = 10
b = a
a -= 1
print(b) #expect to print 9 but it print 10
Run Code Online (Sandbox Code Playgroud)
如何在不改变id的情况下在python中重新赋值变量?
aba*_*ert 13
我不确定你是否对Python中的变量或者不可变值感到困惑.所以我要解释两者,一半的答案可能看起来像"没有,我已经知道了",但另一半应该是有用的.
在Python中 - 例如,C-a变量不是值存在的位置.这只是一个名字.价值观生活在他们想要的任何地方.1所以,当你这样做时:
a = 10
b = a
Run Code Online (Sandbox Code Playgroud)
你没有b参考a.这个想法在Python中甚至没有意义.你正在制作a一个名字10,然后制作b另一个名字10.如果你以后这样做:
a = 11
Run Code Online (Sandbox Code Playgroud)
...你已经a成了一个名字11,但这没有影响b- 它仍然只是一个名字10.
这也意味着,id(a)是不是给你的变量的ID a,因为那里是没有这样的事情.a只是在某个命名空间中查找的名称(例如,模块的全局字典).它是具有ID 的值,11(或者,如果您之前运行它,具有不同的值10).(虽然我们在它:它也是值,而不是变量,是键入的.这里不相关,但值得了解.)
在可变性方面,事情变得有点棘手.例如:
a = [1, 2, 3]
b = a
Run Code Online (Sandbox Code Playgroud)
这仍然是列表的名称a和b两个名称.
a[0] = 0
Run Code Online (Sandbox Code Playgroud)
这不分配a,所以a并b仍然是相同的列表名称.它确实分配给a[0]了该列表的一部分.因此,列表a和b两个名字现在持有[0, 2, 3].
a.extend([4, 5])
Run Code Online (Sandbox Code Playgroud)
这显然做同样的事情:a和b现在的名字列表[0, 2, 3, 4, 5].
事情让人感到困惑:
a += [6]
Run Code Online (Sandbox Code Playgroud)
它是一个重新绑定的赋值a,还是只是改变了a一个名称的值?事实上,这两者都是.在幕后,这意味着:
a = a.__iadd__([6])
Run Code Online (Sandbox Code Playgroud)
...或者,粗略地说:
_tmp = a
_tmp.extend([6])
a = _tmp
Run Code Online (Sandbox Code Playgroud)
所以,我们正在分配a,但我们将相同的值分配给它已经命名的值.与此同时,我们也在改变这个值,这仍然是b名称的价值.
所以现在:
a = 10
b = 10
a += 1
Run Code Online (Sandbox Code Playgroud)
你可能猜到最后一行是这样的:
a = a.__iadd__(1)
Run Code Online (Sandbox Code Playgroud)
这不是真的,因为a没有定义一个__iadd__方法,所以它回到了这个:
a = a.__add__(1)
Run Code Online (Sandbox Code Playgroud)
但那不是重要的一点.2重要的是,因为与列表不同,整数是不可变的.你不能像在INTERCAL中那样把数字10变成数字11,或者(有点像)Fortran,或者你在那里有一个奇怪的梦想,你是最奇怪的X战警.并且没有"变量保持数字10"可以设置为11,因为这不是C++.所以,这必须返回一个新值,即值11.
所以,a成为新的名称11.同时,b仍然是一个名字10.这就像第一个例子.
但是,毕竟这告诉你做你想做的事是多么不可能,我会告诉你做你想做的事是多么容易.
请记住,当我提到您可以改变列表时,该列表的所有名称都会看到新值?那么,如果你这样做了:
a = [10]
b = a
a[0] += 1
Run Code Online (Sandbox Code Playgroud)
现在b[0]将是11.
或者你可以创建一个类:
class Num:
pass
a = Num()
a.num = 10
b = a
a.num += 1
Run Code Online (Sandbox Code Playgroud)
现在,b.num是11.
或者你甚至可以创建一个实现类__add__,并__iadd__和所有其他数字方法,所以它可以容纳数(几乎)透明的,但这样做的性情不定地.
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.
Run Code Online (Sandbox Code Playgroud)
现在:
a = Num(10)
b = a
a += 1
Run Code Online (Sandbox Code Playgroud)
而且b是相同的名称Num(11)作为a.
但是,如果你真的想要这样做,你应该考虑制作一些特定的东西,Integer而不是一个通用的Num东西,它包含任何类似数字的东西,并在numbers模块中使用适当的ABC 来验证你是否涵盖了所有关键方法,以获得许多可选方法的免费实现,并且能够传递isinstance类型检查.(并且可能num.__int__在其构造函数中调用方式int,或者至少是特殊情况,isinstance(num, Integer)因此您最终不会引用对引用的引用...除非这是您想要的.)
嗯,他们住在口译员希望他们住的地方,比如齐奥塞斯库下的罗马尼亚人.但是如果你是用C编写的内置/扩展类型和Party的付费成员,你可以__new__用不依赖于super分配的构造函数覆盖,否则你别无选择.
但它并不完全不重要.按照惯例(当然在所有内置和stdlib类型遵循惯例),__add__不变异,__iadd__确实如此.因此,可变类型如list定义两者,意味着它们获得就地行为a += b但复制行为a + b,而不可变类型仅限于tuple和int定义__add__,因此它们获得两者的复制行为.Python并没有强迫你以这种方式做事,但是如果它没有选择其中的一个,你的类型会很奇怪.如果你熟悉C++,它就是一样的 - 你通常operator+=通过就地变异并返回一个引用来实现this,然后operator+通过复制然后返回+=副本来实现,但语言并没有强迫你这么做,如果你只是混淆了你没有.