python 可变与不可变变量

悠悠 2022-06-06 07:17 505阅读 0赞

不可变变量

操作某个对象时,重新开辟了内存,使其内存地址改变了,我们称其为可变对象,反之称为不可变变量

我们可以通过下面的例子来感受上面的话

  1. x=1
  2. print (id(x))
  3. x+=1
  4. print (id(x))
  5. 52454056
  6. 52454032

id()是一个内置函数,可以返回对象内存地址
同样的变量x经过加操作后地址改变了。

  1. x=1
  2. y=1
  3. z=1
  4. print (id(x))
  5. print (id(y))
  6. print (id(z))
  7. 56976040
  8. 56976040
  9. 56976040

可以发现虽然变量名不同但是地址确实一样的。

这种特性是不是很像某些语言中的字符串。我们称有这种特性的变量为不可变变量(值一旦改变就会占据新的内存空间)。python中的不可变变量有如下

不可变(immutable):int、字符串(string)、float、(数值型number)、元组(tuple),None

优点:
这样可以减少重复的值对内存空间的占用。
缺点:
操作之后就开辟内存的特性。执行效率会有一定程度的降低。

可变变量

我再来看看可变变量

  1. x=[]
  2. y=[]
  3. z=[]
  4. print (id(x))
  5. print (id(y))
  6. print (id(z))
  7. 62566472
  8. 62669960
  9. 62671368

可以看见对于可变变量 ,创建一次就开辟一次地址,不会引用同一块内存

  1. x=[]
  2. print (id(x))
  3. x.append(1)
  4. print (id(x))
  5. 49918024
  6. 49918024

而对同一个对象的操作。内存地址是不会改变的

可变(mutable)变量:字典型(dictionary)、列表型(list)

函数默认参数和None的作用

python 中不可变量为按值传递,可变变量按引用传递

  1. def f(l=[]):
  2. l.append(1)
  3. return l
  4. print (f())
  5. print (f())
  6. print (f())
  7. [1]
  8. [1, 1]
  9. [1, 1, 1]

这是为什么呢?首先我们要知道python 你看见的任何东西都是对象。函数也不例外,默认参数有如下特点

  • 一个函数参数的默认值,仅仅在该函数定义的时候,被赋值一次。
  • 函数默认参数参数在每次调用时指向那个定义时创建的参数

现在我们来验证上上面的结果。
参数的默认值就存在__defaults__中。

  1. def f(l=[]):
  2. l.append(1)
  3. return l
  4. f()
  5. print (f.__defaults__)
  6. f()
  7. print (f.__defaults__)
  8. f()
  9. print (f.__defaults__)
  10. ([1],)
  11. ([1, 1],)
  12. ([1, 1, 1],)

可以看见参数的默认值就在其中,但是是不是同一个呢,现在我们来看看地址

  1. def f(i=1):
  2. print (id(i))
  3. i+=1
  4. print (id(i))
  5. return i
  6. f()
  7. print ('默认值地址:%s'%id(f.__defaults__[0]))
  8. f()
  9. print ('默认值地址:%s'%+id(f.__defaults__[0]))
  10. f()
  11. print ('默认值地址:%s'%+id(f.__defaults__[0]))
  12. 56976040
  13. 56976016
  14. 默认值地址:56976040
  15. 56976040
  16. 56976016
  17. 默认值地址:56976040
  18. 56976040
  19. 56976016
  20. 默认值地址:56976040

可以发现函数定义时默认值就已近存在在__defaults__中了。在调用函数时我们的参数变量就会被赋值为这个__defaults__[0]地址。当.__defaults__[0]是不可变变量时,我们对参数变量进行修改时我们的参数变量地址会变化。这个和我们的逻辑思维一致。
然而,可变变量就不一样了!

  1. def f(l=[]):
  2. print id(l)
  3. l.append(1)
  4. print id(l)
  5. return l
  6. f()
  7. print ('默认值地址:%s'%id(f.__defaults__[0]))
  8. f()
  9. print ('默认值地址:%s'%id(f.__defaults__[0]))
  10. f()
  11. print ('默认值地址:%s'%id(f.__defaults__[0]))
  12. 58904648
  13. 58904648
  14. 默认值地址:58904648
  15. 58904648
  16. 58904648
  17. 默认值地址:58904648
  18. 58904648
  19. 58904648
  20. 默认值地址:58904648

因为可变变量的特性。我们在更改时时不会换地址的,所以我们一直都是在更改__defaults__[0] 。这种特性在默认参数中不是我们想要的。我们用如下的代码完成任务

  1. def f(l=None):
  2. print id(l)
  3. l=[]
  4. l.append(1)
  5. print id(l)
  6. return l
  7. print f()
  8. print ('默认值地址:%s'%id(f.__defaults__[0]))
  9. print f()
  10. print ('默认值地址:%s'%id(f.__defaults__[0]))
  11. print f()
  12. print ('默认值地址:%s'%id(f.__defaults__[0]))
  13. 1792584792
  14. 47296584
  15. [1]
  16. 默认值地址:1792584792
  17. 1792584792
  18. 47296584
  19. [1]
  20. 默认值地址:1792584792
  21. 1792584792
  22. 47296584
  23. [1]
  24. 默认值地址:1792584792

因为None是不可变变量,所以我对他的所有操作都是在新的内存中完成的。这其实也就是None的作用

类中局部变量的注意事项

  1. class b:
  2. x = []
  3. def set(self):
  4. self.x.append(1)
  5. def get(self):
  6. return self.x
  7. for i in range(3):
  8. a = b()
  9. print (b.__dict__)
  10. a.set()
  11. a.get()
  12. {'x': [], '__module__': '__main__', 'set': <function set at 0x0000000002DE5AC8>, '__doc__': None, 'get': <function get at 0x0000000002DE5B38>}
  13. {'x': [1], '__module__': '__main__', 'set': <function set at 0x0000000002DE5AC8>, '__doc__': None, 'get': <function get at 0x0000000002DE5B38>}
  14. {'x': [1, 1], '__module__': '__main__', 'set': <function set at 0x0000000002DE5AC8>, '__doc__': None, 'get': <function get at 0x0000000002DE5B38>}

可以发现每次创建类b时,其中的x都是不一样的。这是因为类的变量被所有实例共享,而变量又是可变变量,所有x都是指向同一个地址。类似c++类中的静态变量,所有类的实例共享一个。而对于不可变变量就不一样了。

  1. class b:
  2. x = 1
  3. def set(self):
  4. self.x+=1
  5. def get(self):
  6. return self.x
  7. for i in range(3):
  8. a = b()
  9. a.set()
  10. print("---------------------------")
  11. print("%d对象地址:%s"%(i,id(a)))
  12. print ("x的值:%d"%a.get())
  13. print("x的地址:%d" % id(a.x))
  14. print("---------------------------")
  15. ---------------------------
  16. 0对象地址:1632001448536
  17. x的值:2
  18. x的地址:1896902144
  19. ---------------------------
  20. ---------------------------
  21. 1对象地址:1632001471096
  22. x的值:2
  23. x的地址:1896902144
  24. ---------------------------
  25. ---------------------------
  26. 2对象地址:1632001448536
  27. x的值:2
  28. x的地址:1896902144
  29. ---------------------------

可以发现,类的”全局变量“是不可变变量,被操纵之后重新占据内存空间。如果想把可变变量变成静态变量(所有类的实例共享一个),我们可以有两个方法。

  1. 使用不可变变量包装一下,比如在原来的代码上进行如下的修改:

    class b:

    1. x = [1]
    2. def set(self):
    3. self.x[0]+=1
    4. def get(self):
    5. return self.x[0]

    for i in range(3):

    1. a = b()
    2. a.set()
    3. print("---------------------------")
    4. print("%d对象地址:%s"%(i,id(a)))
    5. print ("x的值:%d"%a.get())
    6. print("x的地址:%d" % id(a.x))
    7. print("---------------------------")

    D:\Python\learningExampleForPython\venv\Scripts\python.exe D:/Python/learningExampleForPython/example/test.py

    0对象地址:2433377426008
    x的值:2

    x的地址:2433377460872


    1对象地址:2433377448568
    x的值:3

    x的地址:2433377460872


    2对象地址:2433377426008
    x的值:4

    x的地址:2433377460872

    Process finished with exit code 0

  2. 使用global关键字,操作全局变量

    x=1
    class b:

    1. def set(self):
    2. global x
    3. x+=1
    4. def get(self):
    5. global x
    6. return x

    for i in range(3):

    1. a = b()
    2. a.set()
    3. print("---------------------------")
    4. print("%d对象地址:%s"%(i,id(a)))
    5. print ("x的值:%d"%a.get())
    6. print("x的地址:%d" % id(x))
    7. print("---------------------------")

    0对象地址:1739802401032
    x的值:2

    x的地址:1896902144


    1对象地址:1739802401368
    x的值:3

    x的地址:1896902176


    2对象地址:1739802401032
    x的值:4

    x的地址:1896902208

    Process finished with exit code 0

发表评论

表情:
评论列表 (有 0 条评论,505人围观)

还没有评论,来说两句吧...

相关阅读

    相关 python 可变可变变量

    不可变变量 操作某个对象时,重新开辟了内存,使其内存地址改变了,我们称其为可变对象,反之称为不可变变量 我们可以通过下面的例子来感受上面的话 x=1

    相关 可变对象可变对象

    前阵子我们聊了下函数的参数传递以及变量赋值的一些内容:[关于函数参数传递,80%人都错了][80] 简单回顾下要点: 1. Python 中的变量不是装有对象的“容器”,而