对象和基本类型的分配

时间:2011-12-11 12:22:57

标签: python

有这段代码:

# assignment behaviour for integer
a = b = 0
print a, b # prints 0 0
a = 4
print a, b # prints 4 0 - different!

# assignment behaviour for class object
class Klasa:
    def __init__(self, num):
        self.num = num

a = Klasa(2)
b = a
print a.num, b.num # prints 2 2
a.num = 3
print a.num, b.num # prints 3 3 - the same!

问题:

  1. 为什么赋值运算符对基本类型和 class对象(对于基本类型,它按值复制,对于通过引用复制的类对象)?
  2. 如何仅按值复制类对象?
  3. 如何为C ++中的基本类型引用 int& b = a

4 个答案:

答案 0 :(得分:11)

这是许多Python用户的绊脚石。对象引用语义与C程序员习惯的不同。

我们来看第一个案例。当您说a = b = 0时,会创建一个值为int的新0对象,并创建对它的两个引用(一个是a,另一个是b) 。这两个变量指向同一个对象(我们创建的整数)。现在,我们运行a = 4。创建了一个新的int对象值4,并a指向该对象。这意味着,4的引用数量为1,0的引用数量减少了1。

将其与C中的a = 4进行比较,其中a“指向的内存区域被写入。 C中的a = b = 4表示4被写入两个内存 - 一个用于a,另一个用于b

现在第二种情况a = Klass(2)创建一个类型为Klass的对象,将其引用计数增加一,并使a指向它。 b = a只需要a指向的内容,使b指向相同的内容,并将该引用的引用计数增加一。这与你a = b = Klass(2)时的情况相同。由于您要取消引用同一对象并打印属性值,因此尝试打印a.numb.num是相同的。您可以使用id内置函数来查看对象是否相同(id(a)id(b)将返回相同的标识符)。现在,您可以通过为其中一个属性赋值来更改对象。由于ab指向同一个对象,因此当通过ab访问对象时,您希望值的更改可见。这就是它的确切方式。

现在,回答你的问题。

  1. 对于这两者,赋值运算符的工作方式不同。它只是添加对RValue的引用并使LValue指向它。 总是“通过引用”(虽然这个术语在参数传递的上下文中比在简单的赋值中更有意义)。
  2. 如果您想要副本对象,请使用copy module
  3. 正如我在第1点所说,当你做作业时,你总是转移参考。除非您要求复制,否则永远不会复制。

答案 1 :(得分:6)

引自Data Model

  

对象是Python的数据抽象。 Python中的所有数据   程序由对象或对象之间的关系表示。 (在   一种感觉,并符合冯诺依曼的“存储”模型   程序计算机,“代码也由对象表示。”

从Python的角度来看,Fundamental data type与C / C ++有根本的不同。它用于将C/C++数据类型映射到Python。所以让我们暂时离开讨论,并考虑所有数据都是对象并且是某些类的表现。每个对象都有一个ID(有点像地址),Value和Type。

通过引用复制所有对象。对于前

>>> x=20
>>> y=x
>>> id(x)==id(y)
True
>>>

拥有新实例的唯一方法是创建一个。

>>> x=3
>>> id(x)==id(y)
False
>>> x==y
False

这可能听起来很复杂,但为了简化一点,Python使一些类型不可变。例如,您无法更改string。您必须对其进行切片并创建一个新的字符串对象。

通常通过引用进行复制会给ex。

带来意想不到的结果

x=[[0]*8]*8可能会让您感觉它会创建一个0的二维列表。但实际上它创建了一个相同列表对象[0]的引用列表。所以做x [1] [1]最终会同时改变所有重复的实例。

Copy模块提供了一个名为deepcopy的方法来创建对象的新实例,而不是浅实例。当您打算拥有两个不同的对象并按照您在第二个示例中的预期单独操作时,这非常有用。

扩展您的示例

>>> class Klasa:
    def __init__(self, num):
         self.num = num


>>> a = Klasa(2)
>>> b = copy.deepcopy(a)
>>> print a.num, b.num # prints 2 2
2 2  
>>> a.num = 3
>>> print a.num, b.num # prints 3 3 - different!
3 2

答案 2 :(得分:1)

它的工作方式不同。在第一个示例中,您更改了 a ,以便 a b 引用不同的对象。在你的第二个例子中,你没有,所以 a b 仍然引用同一个对象。

顺便说一下,整数是不可变的。您无法修改其值。您所能做的就是创建一个新的整数并重新引用您的引用。 (就像你在第一个例子中所做的那样)

答案 3 :(得分:1)

假设你和我有一个共同的朋友。如果我认为我不再喜欢她,她仍然是你的朋友。另一方面,如果我给她一份礼物,你的朋友会收到礼物。

分配不会复制Python中的任何内容,“按引用复制”介于尴尬和无意义之间(正如您在其中一条评论中指出的那样)。赋值使变量开始引用值。 Python中没有单独的“基本类型”;虽然其中一些是内置的,int仍然是一个类。

两个情况下,赋值会使变量引用右侧评估的内容。根据隐喻,您所看到的行为正是您在该环境中应该期待的行为。无论您的“朋友”是int还是Klasa,分配属性与将变量重新分配给完全其他实例的行为完全不同,具有相应的不同行为。

唯一真正的区别是int没有碰巧有你可以分配的任何属性。 (这是实施实际上必须有一点魔力限制你的部分。)

你混淆了两个不同的“参考”概念。 C ++ T&是一个神奇的东西,当被赋予它时,就地更新引用的对象,而不是引用本身;初始化参考后,永远不能“重新安置”。这在大多数事物都是价值的语言中很有用。在Python中,一切都是开头的参考。 Pythonic引用更像是一个始终有效,永不为null,不可用于算术,自动解除引用的指针。赋值使引用开始完全引用不同的东西。你不能通过替换它来“就地更新引用的对象”,因为Python的对象就是不能那样工作。当然,您可以通过播放其属性来更新其内部状态(如果有任何可访问的属性),但这些属性本身也是所有引用。