Discuss / Python / 为什么对象属性不同步更新?

为什么对象属性不同步更新?

Topic source

Blown_Wang

#1 Created at ... [Delete] [Delete and Lock User]

我看了另外一个介绍,里面有这样一段代码:

class Cat(object):
   def __init__(self):
      self.start = 0
      self.end = 0
      self.time = (self.start, self.end)   # 并不会同步更新

   def setStart(self, value):
      self.start = value

   def setEnd(self, value):
      self.end = value
   
   def get(self):
      return self.start,self.end


if __name__ == '__main__':
    cat = Cat()
    cat.setStart(1)
    cat.setEnd(10)
    print(cat.time)        # (0, 0)
    print(cat.get())       #(1, 10)

对象方法设置了对象属性,为什么打印出来还是原来值,而get函数返回的结果是正确?这是什么原因呢?

廖雪峰

#2 Created at ... [Delete] [Delete and Lock User]

你要深刻理解变量的本质,就要意识到self.start, self.end, self.time都是变量

我们通常说self.start是个对象,实际上说self.start指向一个对象,该对象就是数值0

执行self.time = (self.start, self.end)会立刻得到结果(0, 0),由self.time变量指向该对象(0,0)

可以看一个更复杂的例子:

>>> L = [1, 2, 3]
>>> s = 'end'
>>> t = (L, s)
>>> t
([1, 2, 3], 'end')
>>> L[0] = 'A'
>>> t
(['A', 2, 3], 'end')
>>> s = 'start'
>>> t
(['A', 2, 3], 'end')

t指向的两个元素是List和'end',如果修改L,相当于修改了List,t的值会变,如果“修改”s,仅仅是改变了s的指向(指向新的'start'),不影响t的第二个元素'end',所以t不变

要注意区分修改对象:L[0] = 'A'

未修改对象,仅指向了新的对象:s = 'end'

之所以不能改变t的第二个元素'end',是因为str对象没有提供任何修改方法。

廖雪峰

#3 Created at ... [Delete] [Delete and Lock User]

如果想要修改str,就必须让str提供修改自己内部状态的方法,python不支持修改str,但ruby可以修改str,在ruby下可以看到修改str的效果:

irb(main):001:0> s = "Hello"
=> "Hello"
irb(main):002:0> arr = Array[s, s]
=> ["Hello", "Hello"]
irb(main):003:0> s.concat(" World")
=> "Hello World"
irb(main):004:0> arr
=> ["Hello World", "Hello World"]

renjie0310

#4 Created at ... [Delete] [Delete and Lock User]

还是没搞明白,把元组换成数据,还是不变的,廖老师的例子还是没看明白,悟性太低了,愁。

renjie0310

#5 Created at ... [Delete] [Delete and Lock User]

理解不知道对不对呀,TIME=[TIME[0]=start,TIME[1]=end],第二次赋值时,TIME[0]/[1]没变,所以值不变。就如同:

a=1

b=a

a=2,这时b的值是不受影响的。

颜名48715

#6 Created at ... [Delete] [Delete and Lock User]

我觉得题主的疑问可能是关于惰性求值的,大多数语言包括Python都是及早求值的,所以self.time初始就是(0,0)。廖老师回复里的例子解释的是用int和list赋值时拷贝构造函数的不同,一个是直接拷贝赋值,一个是浅拷贝赋值,实际上复制的是一个指向同一个list对象的指针,个人理解不一定正确。

我的理解是:由于修改在元祖中的list的值并不会修改这个list的内存地址,所以才能修改。如果修改值会影响该变量的id,那么python便不允许修改。比如以下代码:

l=[1,2,3]print(id(l))l[0]="a"print(id(l))

结果是 

140539077830464
140539077830464

可以看到,修改list并不会影响此list的id,仅仅将值改变了。但是python还有一些一旦初始化,就不会被允许改变的事物,比如int,string,元祖(元祖较为特殊,可以嵌套list)等等。

这些不可改变的原因是:这些变量的值与其内存地址挂钩或者说他们的id挂钩。比如以下代码:

a=1print("a:",a,id(a))b=aprint("b:",b,id(b))a=2print("a:",a,id(a))print("b:",b,id(b))

结果为

a: 1 4488313536
b: 1 4488313536
a: 2 4488313568
b: 1 4488313536

可以看到,一旦这些不被允许改变的变量被初始化之后,他们的内存地址/id便不再发生改变。(将a重新赋值后,b并没有随之重新赋值,而是仍然指向1的内存地址。)

元祖也是不可改变的类型,但是仅仅是固定住了 元祖的内存地址/id 及其内部元素的内存地址/id。但是我们知道,如果修改list,是不会改变list本身内存地址或者id,所以,此元祖的每个元素的内存地址/id都没发生改变,元祖的id与内存地址也没有发生改变,那么修改就成功。比如以下代码:

l=[1,2,3]print(id(l))l[0]="a"print(id(l))t=(l,"ABC")print(id(t))l[0]=333print(id(t))t[0][1]=22print(t)


#=============结果====================
140393081428416
140393081428416
140393080520000
140393080520000
([333, 22, 3], 'ABC')

可以看到,修改元祖t中的list l的值,并没有影响l的id,也没有影响t的id,所以修改成功。但是如果尝试修改元祖中的字符串元素,相当于新建了一个字符串变量并且重新赋于内存地址和id,元祖的id/内存地址就会发生改变,所以无法修改。

HHHHHHHpz

#8 Created at ... [Delete] [Delete and Lock User]
class Cat(object):
   def __init__(self):
      self.start = 0
      self.end = 0
      self.time = (self.start, self.end)   # 并不会同步更新

   def setStart(self, value):
      self.start = value

   def setEnd(self, value):
      self.end = value
   
   def get(self):
      return self.start,self.end


if __name__ == '__main__':
    cat = Cat()#创造了cat.time变量,其指向(0,0);cat.start和cat.end均指向0
    cat.setStart(1)#cat.start改为指向1
    cat.setEnd(10)#cat.end改为指向10
    #但此时cat.time仍然指向(0,0)
    print(cat.time)        # (0, 0)  
    print(cat.get())       #(1, 10) 由于return的是cat.start和cat.end,其指向均改变,因此为正确答案

简单点和下面的代码是一样的,即使换成列表,time在一开始就确定了指向[0,0]后续对start和end重新赋值,只是改变了start和end从指向0改为指向1,但是并未对time的元素进行修改,廖老师list和tuple那一章节有个图描述的很好

start = 0end = 0time = [start, end]  # 将元组换成列表start = 1end = 1print(time)  # 结果是(0,0)time = [start, end]  # 将元组换成列表print(time)  # 结果是(1,1)print(start)  # 1print(end)  # 1

  • 1

Reply