为什么变量=对象不像变量=数字那样工作

时间:2015-04-28 17:49:21

标签: python python-2.7 variables object python-3.x

这些变量赋值按我的预期工作:

>>> a = 3
>>> b = a
>>> print(a, b)
(3, 3)
>>> b=4
>>> print(a, b)
(3, 4)

但是,这些分配的行为有所不同:

>>> class number():
...     def __init__(self, name, number):
...         self.name = name
...         self.number = number
... 
>>> c = number("one", 1)
>>> d = c
>>> print(c.number, d.number)
(1, 1)
>>> d.number = 2
>>> print(c.number, d.number)
(2, 2)

为什么cd相同,与(a, b)示例不同?如何在(a, b)类示例中的(c, d)中执行此操作?也就是说,复制对象然后更改它的一部分(这不会影响我借用属性的对象)?

10 个答案:

答案 0 :(得分:16)

这些行:

c = number("one", 1)
d = c

......实际上是:

  • 创建number的新实例并将其分配给c
  • 将名为c的现有引用分配给新变量d

您尚未更改或修改有关c的任何内容; d是另一个指向同一实例的名称。

如果不克隆实例或创建新实例,则无法执行与原始int的行为类似的任何操作。

为了纠正一些信息,上面的解释是rather simplified并且有点不完整in its nature,尽管主要描述了在10,000英尺处发生的事情

仔细看看,我们必须了解一些关于Python变量,"名称"以及它们如何与该程序交互的内容。

如上所述,you have the notion of "names" and "bindings",非常简单明了:

a = 3
b = a

在此上下文中,a是名称,b是对a的绑定。我们尚未对a进行任何修改或更改。

如前所述,Python中有两种类型的数据:可变和不可变。指向不可变数据的名称(例如基元和元组)可以重新分配,而不会对其上存在的任何其他绑定产生任何不良影响,因为没有状态在绑定方面发生变化。

这就是为什么这次重新分配符合我们的期望:

print(a, b)
b = 4
print(a, b)

b = 4的结果是b现在指向一个整数的新副本,值为4.

回想一下,我确实提到了元组作为不可变数据。您无法更改元组中特定实体的绑定...

t = ('foo', 'bar')
t[0] = 'baz' # illegal

...但可以将可变数据结构作为这些绑定的一部分。

t = ([1, 2, 3], 'bar')
t[0].append([4, 5, 6]) # ([1, 2, 3, [4, 5, 6]], 'bar')

那么这就离开了我们的榜样?

c = number("one", 1)
d = c

number可变类型,其名称为c,其值可以在c的多个不同绑定之间随意更改。

实际上,我们已经获得了名称和对名称的绑定:

  • 我们有number的新实例,并以名称c引用它。
  • 将引用c绑定到另一个名称d

同样,c没有任何改变,但可以通过其他名称引用。

与不可变数据不同,当我们重新分配d.number的值时,我们会重新分配c知道的相同绑定:

>>> id(d.number)
36696408
>>> id(c.number)
36696408

这就是要求新实例或副本的原因。您必须引用number的其他实例。通过这种简单的绑定,您无法实现这一目标。

from copy import copy
c = number("one", 1)
d = copy(c)
id(c) # 140539175695784
id(d) # 140539175695856

答案 1 :(得分:10)

一张价值千言万语的图片

a = 3
b = a
c = number("one", 1)
d = c

Block-and-pointer diagram, before

第2步......

b = 4
d.number = 2

Block-and-pointer diagram, after

您可以看到为什么更改d.number也会影响c

如果在步骤2之前,你做了

import copy
d = copy.copy(c)

...然后cd是独立的。更改d.number不会影响c

Block-and-pointer diagram, after copying

答案 2 :(得分:9)

我没有看到有人通过复制对象而不是仅仅为同一个对象分配新的引用来提供有关如何使这两个案例工作相同的详细信息。

import copy
c = number("one", 1)
d = c
e = copy.copy(c)

print(c.number, d.number, e.number)
d.number = 2
e.number = 5
print(c.number, d.number, e.number)

这会给你:

1 1 1
2 2 5

答案 3 :(得分:7)

你关注的是这两对线是相同的(都使用普通=):

# one
a = 3
b = a

#two
c = number("one", 1)
d = c

您缺少的是这两行不同:

# one
b = 4

# two
d.number = 2

它们不相同的原因是d.number中有一个点,b没有。

设置d = c 与settign b = a具有相同的效果。不同之处在于,d.number = 2 与执行b = 4相同。执行b = 4时,为名称b分配新对象。执行d.number = 2时,您可以修改名称d已引用的对象,而无需指定新对象。如果您将第二个示例更改为d = 2,则使用简单分配而不是属性分配,您会看到c不受影响,就像第一个示例中a不受影响一样。

虽然它可能令人困惑,但=在Python的所有上下文中并不总是意味着同样的事情。分配裸名称(blah = ...)与分配属性(blah.attr = ...)或项目(blah[item] = ...)不同。要理解=的含义,您需要查看左侧(分配目标)以查看它是裸名还是某种表达式。

至于如何在c / d示例中获得a / b示例的效果,它取决于您想要的确切效果。如果您希望dc指向具有不同number属性的不同对象,您可以执行以下操作:

d = number("one", 2)

请注意,这现在与b = 4并行(因为它使用了对裸名称的赋值)。还有其他更复杂的解决方案涉及制作现有d对象的副本;查看标准库中的copy模块。究竟该做什么取决于你想用代码完成什么。

答案 4 :(得分:5)

在Python中,一切都是参考。拿这个,例如:

b = 4

现在,当你这样做时:

c = number("one", 1)
d = c
...
d.number = 2

您只是更改了数字b 引用。不是数字本身。现在,在后面的例子中:

d = c

d.number将d设置为引用c。现在,当您设置grep时,您将在c和d引用的对象上设置属性。就像我说的,一切都是参考。

在Python中,变量更像是标签(或者,在C术语中,指针):它们不是一个值。他们只是指出真正的价值。

答案 5 :(得分:2)

这种差异的原因是不可变的v。不可变对象 - int是一个不可变对象,你的类是一个可变对象。

在此示例中(类似于您的第一个示例),xy都指向相同的不可变对象(整数1)。该对象永远不会更改,因此当您分配y = 2时,y现在指向不同的不可变对象(int(2))。

>>> x = 1  # x points to the object int(1).
>>> y = x  # So does y.
>>> assert x is y
>>> y = 2
>>> assert x is not y
>>>    

您的类是一个可变对象,类似于列表。在第二个示例中(类似于您的第二个示例),xy都指向同一个可变对象([])。当您将0附加到该列表时,会将其放在它们都引用的列表中。当您将[0]分配给y时,您正在为y分配不同的列表,但该列表恰好等于x,即使它是&#39 ;另一个不同的对象。

>>> x = []
>>> y = x
>>> assert x is y  # x and y reference the same object.
>>> y.append(0)  # We append 0 to the list that both x and y reference.
>>> x
[0]
>>> y
[0]
>>>
>>> y = [0]  # Creating a new list object.
>>> assert x is not y  # x and y are different objects...
>>> assert x == y  # ...but they are equal.
>>>    

答案 6 :(得分:2)

  

"名字中有什么?我们称之为玫瑰的那个

     

任何其他名称都会闻到甜味;"

     

---第二幕,第二幕罗密欧朱丽叶

在Python中,

  • 变量只是对象或文字的符号引用
  • 对象是类型或类的实例
  • 除非明确强制执行,否则所有变量都通过引用(赋值,参数传递和函数返回值)复制

现在让我们了解你的例子

a和b指的是两个不同的整数文字

a = 3
b = a

print(a, b)

b现在引用一个新的整数文字(4)

b=4

c指的是一个对象,它是类number

的一个实例
c = number("one", 1)

d指的是c指向

的同一个对象
d = c

更改d引用的对象的number属性。由于d和c指的是同一个对象,这也会影响c。

d.number = 2

答案 7 :(得分:2)

  

为什么c与d相同,与(a,b)示例不同?

在python中,变量不是对象 “哈姆雷特不是莎士比亚写的;它只是由一个名叫莎士比亚的人写的。“ Python在一个东西和我们用来引用那个东西的标签之间做出了至关重要的区分。 “这个名叫莎士比亚的男人”是一个男人。 “莎士比亚”只是一个名字。如果我们这样做:

l = []

然后[]是空列表。 l是一个指向空列表的变量,但l本身不是空列表。

enter image description here

因此,在python变量中工作更像是一个标记。在Python中进行赋值时,它会使用变量名称标记值。

a = 1

enter image description here

如果更改变量的值,它只会将标记更改为内存中的新值。

enter image description here enter image description here

将一个变量分配给另一个变量会使新标记绑定到与下面显示的相同的值。

b = a  

enter image description here

如果您为3分配了新值b,那么它将绑定到该新值,而a仍会绑定到2

在第二个代码段中,d = c会使cd都指向同一个对象。修改任何对象成员将导致该成员指向新值,而不是名称ab。他们仍然指向同一个对象。

  

如何在(a, b)类示例中的(c, d)中执行某些操作?也就是说,复制对象,然后更改它的一部分(这不会影响我借用属性的对象)?

Python 2.7.10:8.17. copy — Shallow and deep copy operations

  

Python中的赋值语句不复制对象,它们在目标和对象之间创建绑定。对于可变或包含可变项的集合,有时需要一个副本,因此可以更改一个副本而不更改另一个副本。该模块提供了通用的浅层和深层复制操作(如下所述)。

     

接口摘要:

copy.copy(x)
     

返回x的浅表副本。

copy.deepcopy(x)
     

返回x的深层副本。

>>> import copy
>>> c = number("one", 1)
>>> d = copy.copy(c)        # Make a copy of object c  
>>> id(d), id(c)
(42898856, 43279384)  

修改cd对象中的任何一个都不会相互影响。

参考文献:

答案 8 :(得分:1)

为变量分配对象引用完全,就像为变量赋值一样。他们中的任何一个都涉及复制任何东西。它在Python的知识祖先Lisp和Smalltalk中完全相同。只是数字不能被改变。

>>> x = 10**50
>>> y = 10**50
>>> z = x
>>> x is y
False
>>> x is z
True

我们在这里看到Python区分制作副本(x和y相等但不同)而不制作副本(x是z),并且分配数字不会制作副本。

答案 9 :(得分:0)

这在其他答案中并未澄清,但如果我没记错,python中的整数实际上并不是原语。它们是不可变的整数对象。因此,当您有多个变量都持有值3时,实际上您对包含值3的单个Integer实例有多个引用。

所以考虑到这一点;

a = 3
b = a

结果变量a和b各自指向保持值为3的整数。同样,

c = number("one", 1)
d = c

结果变量c和d各自指向相同的数字实例。

同样的事情发生了
b = 4

现在b是对整数4的引用。

但是,设置d.number时

d.number = 2

c和d指向的类“number”的实例的成员变量“number”被更新为引用Integer 2.这里的重要概念是你正在修改一个成员变量由两个单独的变量引用的对象。

来源: http://www.laurentluce.com/posts/python-integer-objects-implementation/