如何在python中重新分配变量而不更改其ID? [英] How to re-assign a variable in python without changing its id?

查看:33
本文介绍了如何在python中重新分配变量而不更改其ID?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当将一个变量分配给另一个变量时,它们指向同一个对象.那么,如何在变量仍指向同一个对象的情况下更改其中一个的值?

When assigning a variable to another, they point to the same object. So, how do I change the value for one of them with the variables still pointing to the same object?

a = 10
b = a
a -= 1
print(b) #expect to print 9 but it print 10

如何在python中重新分配一个变量而不改变它的id?

How to re-assign a variable in python without changing its id?

推荐答案

我不确定您是否对 Python 中的变量或不可变值感到困惑.所以我将解释两者,一半的答案可能看起来像是不,我已经知道了",但另一半应该有用.

I'm not sure whether you're confused about variables in Python, or about immutable values. So I'm going to explain both, and half the answer will probably seem like "no duh, I already knew that", but the other half should be useful.

在 Python 中——与 C 不同——变量不是值所在的位置.这只是一个名字.价值观存在于他们想要的任何地方.1所以,当你这样做时:

In Python—unlike, say, C—a variable is not a location where values live. It's just a name. The values live wherever they want to.1 So, when you do this:

a = 10
b = a

您没有将 b 变成对 a 的引用.这个想法在 Python 中甚至没有意义.您正在将 a 变成 10 的名称,然后将 b 变成 10 的另一个名称.如果你以后这样做:

You're not making b into a reference to a. That idea doesn't even make sense in Python. You're making a into a name for 10, and then making b into another name for 10. And if you later do this:

a = 11

... 你已经将 a 变成了 11 的名字,但这对 b 没有影响——它仍然只是一个名字10.

… you've made a into a name for 11, but this has no effect on b—it's still just a name for 10.

这也意味着 id(a) 没有给你变量 a 的 ID,因为没有这样的东西.a 只是在某个命名空间(例如,模块的全局字典)中查找的名称.它是具有 ID 的 value11(或者,如果您之前运行过它,则是不同的值 10).(虽然我们在此:键入的也是值,而不是变量.此处不相关,但值得了解.)

This also means that id(a) is not giving you the ID of the variable a, because there is no such thing. a is just a name that gets looked up in some namespace (e.g., a module's globals dict). It's the value, 11 (or, if you ran it earlier, the different value 10) that has an ID. (While we're at it: it's also values, not variables, that are typed. Not relevant here, but worth knowing.)

在可变性方面,事情变得有点棘手.例如:

Things get a bit tricky when it comes to mutability. For example:

a = [1, 2, 3]
b = a

这仍然使 ab 都是列表的名称.

This still makes a and b both names for a list.

a[0] = 0

这不会分配给 a,所以 ab 仍然是同一个列表的名称.它确实分配给a[0],它是该列表的一部分.所以,ab 的列表现在包含 [0, 2, 3].

This doesn't assign to a, so a and b are still names for the same list. It does assign to a[0], which is part of that list. So, the list that a and b both name now holds [0, 2, 3].

a.extend([4, 5])

这显然做了同样的事情:ab 现在将列表命名为 [0, 2, 3, 4, 5].

This obviously does the same thing: a and b now name the list [0, 2, 3, 4, 5].

这里是事情变得混乱的地方:

Here's where things get confusing:

a += [6]

它是重新绑定 a 的赋值,还是只是改变了 a 作为名称的值?事实上,两者兼而有之.这意味着,在幕后,是:

Is it an assignment that rebinds a, or is it just mutating the value that a is a name for? In fact, it's both. What this means, under the covers, is:

a = a.__iadd__([6])

……或者,粗略地说:

_tmp = a
_tmp.extend([6])
a = _tmp

因此,我们正在分配给a,但我们将分配给它的值与它已经命名的值相同.同时,我们也在改变那个值,它仍然是 b 命名的值.

So, we are assigning to a, but we're assigning the same value back to it that it already named. And meanwhile, we're also mutating that value, which is still the value that b names.

现在:

a = 10
b = 10
a += 1

你可能会猜到最后一行是这样的:

You probably can guess that the last line does something like this:

a = a.__iadd__(1)

这不完全正确,因为 a 没有定义 __iadd__ 方法,所以它回退到这个:

That's not quite true, because a doesn't define an __iadd__ method, so it falls back to this:

a = a.__add__(1)

但这不是重要的一点.2 重要的一点是,因为整数与列表不同,是不可变的.你不能把数字 10 变成数字 11,就像你在 INTERCAL 或(某种)Fortran 中那样,或者你做最奇怪的 X 战警时所做的那个奇怪的梦.并且没有可以设置为 11 的保存数字 10 的变量",因为这不是 C++.因此,这 必须 返回一个新值,即值 11.

But that's not the important bit.2 The important bit is that, because integers, unlike lists, are immutable. You can't turn the number 10 into the number 11 the way you could in INTERCAL or (sort of) Fortran or that weird dream you had where you were the weirdest X-Man. And there's no "variable holding the number 10" that you can set to 11, because this isn't C++. So, this has to return a new value, the value 11.

因此,a 成为新 11 的名称.同时,b 仍然是 10 的名称.就像第一个例子一样.

So, a becomes a name for that new 11. Meanwhile, b is still a name for 10. It's just like the first example.

但是,毕竟这告诉你做你想做的事是多么不可能,我要告诉你做你想做的事是多么容易.

But, after all this telling you how impossible it is to do what you want, I'm going tell you how easy it is to do what you want.

还记得之前,当我提到您可以对列表进行变异时,该列表的所有名称都会看到新值吗?那么,如果您这样做会怎样:

Remember earlier, when I mentioned that you can mutate a list, and all the names for that list will see the new value? So, what if you did this:

a = [10]
b = a
a[0] += 1

现在 b[0] 将是 11.

或者你可以创建一个类:

Or you can create a class:

class Num:
    pass

a = Num()
a.num = 10
b = a
a.num += 1

现在,b.num11.

或者您甚至可以创建一个实现 __add____iadd__所有其他数字方法,因此它可以(几乎)透明地保存数字,但可以可变地保存.

Or you can even create a class that implements __add__ and __iadd__ and all the other numeric methods, so it can hold numbers (almost) transparently, but do so mutably.

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 是与 a 相同的 Num(11) 的名称.

And b is a name for the same Num(11) as a.

如果你真的想这样做,你应该考虑制作一些特定的东西,比如 Integer 而不是一个通用的 Num 来保存任何像数字一样的东西,并且在 numbers 模块中使用适当的 ABC 来验证您涵盖了所有关键方法,获得了许多可选方法的免费实现,并且能够通过 isinstance 类型检查.(并且可能像 int 那样在其构造函数中调用 num.__int__ ,或者至少是特殊情况 isinstance(num, Integer) 所以你不要以对引用的引用结束……除非这是您想要的.)

If you really want to do this, though, you should consider making something specific like Integer rather than a generic Num that holds anything that acts like a number, and using the appropriate ABC in the numbers module to verify that you covered all the key methods, to get free implementations for lots of optional methods, and to be able to pass isinstance type checks. (And probably call num.__int__ in its constructor the way int does, or at least special-case isinstance(num, Integer) so you don't end up with a reference to a reference to a reference… unless that's what you want.)

1.好吧,他们住在译员希望他们住的地方,就像齐奥塞斯库统治下的罗马尼亚人一样.但是,如果您是用 C 编写的内置/扩展类型并且是 Party 的付费成员,则可以使用不依赖于 super__new__> 分配,否则你别无选择.

2.但这并非完全不重要.按照约定(当然,所有内置和 stdlib 类型都遵循约定),__add__ 不会变异,__iadd__ 会.因此,像 list 这样的可变类型定义了两者,这意味着它们为 a += b 获得了就地行为,但为 a + b 获得了复制行为,而像 tupleint 这样的不可变类型只定义了 __add__,所以它们对两者都有复制行为.Python 不会强迫您以这种方式做事,但是如果它不选择这两种方式中的一种,您的类型就会很奇怪.如果您熟悉 C++,那也是一样的——您通常通过就地变异并返回对 thisoperator+ 的引用来实现 operator+=code> 通过复制然后在副本上返回 += ,但是语言不会强迫您这样做,如果您不这样做,只会令人困惑.

2. But it's not completely unimportant. By convention (and of course in all builtin and stdlib types follow the convention), __add__ doesn't mutate, __iadd__ does. So, mutable types like list define both, meaning they get in-place behavior for a += b but copying behavior for a + b, while immutable types like tuple and int define only __add__, so they get copying behavior for both. Python doesn't force you to do things this way, but your type would be very strange if it didn't pick one of those two. If you're familiar with C++, it's the same—you usually implement operator+= by mutating in-place and returning a reference to this, and operator+ by copying and then returning += on the copy, but the language doesn't force you to, it's just confusing if you don't.

这篇关于如何在python中重新分配变量而不更改其ID?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆