如何在不改变id的情况下在python中重新赋值变量?

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)

这仍然是列表的名称ab两个名称.

a[0] = 0
Run Code Online (Sandbox Code Playgroud)

这不分配a,所以ab仍然是相同的列表名称.它确实分配给a[0]了该列表的一部分.因此,列表ab两个名字现在持有[0, 2, 3].

a.extend([4, 5])
Run Code Online (Sandbox Code Playgroud)

这显然做同样的事情:ab现在的名字列表[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.num11.


或者你甚至可以创建一个实现类__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,而不可变类型仅限于tupleint定义__add__,因此它们获得两者的复制行为.Python并没有强迫你以这种方式做事,但是如果它没有选择其中的一个,你的类型会很奇怪.如果你熟悉C++,它就是一样的 - 你通常operator+=通过就地变异并返回一个引用来实现this,然后operator+通过复制然后返回+=副本来实现,但语言并没有强迫你这么做,如果你只是混淆了你没有.