本文分为如下几个部分

  • 概念
  • 地址问题
  • 作为函数参数
  • 可变参数在类中使用
  • 函数默认参数
  • 类的实现上的差异

概念

可变对象与不可变对象的区别在于对象本身是否可变。

python内置的一些类型中

  • 可变对象:list dict set
  • 不可变对象:tuple string int float bool

举一个例子

  1. # 可变对象
  2. >>> a = [1, 2, 3]
  3. >>> a[1] = 4
  4. >>> a
  5. [1, 4, 3]
  6. # 不可变对象
  7. >>> b = (1, 2, 3)
  8. >>> b[1] = 4
  9. Traceback (most recent call last):
  10. File "<stdin>", line 1, in <module>
  11. TypeError: 'tuple' object does not support item assignment

上面例子很直观地展现了,可变对象是可以直接被改变的,而不可变对象则不可以。

地址问题

下面我们来看一下可变对象的内存地址变化

  1. >>> a = [1, 2, 3]
  2. >>> id(a)
  3. 2139167175368
  4. >>> a[1] = 4
  5. >>> id(a)
  6. 2139167175368

我们可以看到,可变对象变化后,地址是没有改变的

如果两个变量同时指向一个地址

1.可变对象

  1. >>> a = [1, 2, 3]
  2. >>> id(a)
  3. 2139167246856
  4. >>> b = a
  5. >>> id(b)
  6. 2139167246856
  7. >>> a[1] = 4
  8. >>> a
  9. [1, 4, 3]
  10. >>> b
  11. [1, 4, 3]
  12. >>> id(a)
  13. 2139167246856
  14. >>> id(b)
  15. 2139167246856

我们可以看到,改变ab也跟着变,因为他们始终指向同一个地址

2.不可变对象

  1. >>> a = (1, 2, 3)
  2. >>> id(a)
  3. 2139167074776
  4. >>> b = a
  5. >>> a = (4, 5, 6)
  6. >>> a
  7. (4, 5, 6)
  8. >>> b
  9. (1, 2, 3)
  10. >>> id(a)
  11. 2139167075928
  12. >>> id(b)
  13. 2139167074776

我们可以看到,a改变后,它的地址也发生了变化,而b则维持原来的地址,原来地址中的内容也没有发生变化。

作为函数参数

1.可变对象

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

我们可以看到,可变对象作为参数传入时,在函数中对其本身进行修改,是会影响到全局中的这个变量值的,因为函数直接对该地址的值进行了修改。

2.不可变对象

  1. >>> def myfunc(a):
  2. ... a += 1
  3. ... print(a)
  4. ...
  5. >>> a = 2
  6. >>> myfunc(a)
  7. 3
  8. >>> a
  9. 2

对于不可变对象来说,虽然函数中的a值变了,但是全局中的a值没变,因为函数中的a值已经对应了另外一个地址,而全局中的a值指向的原来地址的值是没有变的。

3.总结

python中向函数传递参数只能是引用传递,表示把它的地址都传进去了,这才会带来上面的现象。

有的编程语言允许值传递,即只是把值传进去,在里面另外找一个地址来放,这样就不会影响全局中的变量。

可变参数在类中使用

我们直接来看下面这个例子

  1. class Myclass:
  2. def __init__(self, a):
  3. self.a = a
  4. def printa(self):
  5. print(self.a)

运行如下

  1. >>> aa = [1,2]
  2. >>> my = Myclass(aa)
  3. >>> my.printa()
  4. [1, 2]
  5. >>> aa.append(3)
  6. >>> my.printa()
  7. [1, 2, 3]

我们可以看到,类中的变量和全局变量地址依然是共用的,无论在哪里修改都会影响对方。

其实这个特性也不能说是一个弊端,利用这一点可以进行一些很方便的操作,比如两个线程同时操作一个队列,我们不用设置一个global队列,只要将队列这个可变对象传入类之中,修改就会自动同步。

下面这个生产者消费者例子就是这样

  1. import time
  2. import threading
  3. import random
  4. from queue import Queue
  5.  
  6. class Producer(threading.Thread):
  7.  
  8. def __init__(self, queue):
  9. threading.Thread.__init__(self)
  10. self.queue = queue
  11.  
  12. def run(self):
  13. while True:
  14. random_integer = random.randint(0, 100)
  15. self.queue.put(random_integer)
  16. print('add {}'.format(random_integer))
  17. time.sleep(random.random())
  18.  
  19. class Consumer(threading.Thread):
  20.  
  21. def __init__(self, queue):
  22. threading.Thread.__init__(self)
  23. self.queue = queue
  24.  
  25. def run(self):
  26. while True:
  27. get_integer = self.queue.get()
  28. print('lose {}'.format(get_integer))
  29. time.sleep(random.random())
  30.  
  31. def main():
  32. queue = Queue()
  33. th1 = Producer(queue)
  34. th2 = Consumer(queue)
  35. th1.start()
  36. th2.start()
  37.  
  38. if __name__ == '__main__':
  39. main()

queue传入两个类中,在两个类中随意更改,自动在两个类间同步。

函数默认参数

函数默认参数一定要设定为不可变参数,否则会引发一些错误,我们来看下面一个例子

  1. >>> def myfunc(l=[]):
  2. ... l.append('add')
  3. ... print(l)
  4. ...
  5. >>> myfunc([1, 2, 3])
  6. [1, 2, 3, 'add']
  7. >>> myfunc(['a', 'b'])
  8. ['a', 'b', 'add']

上面代码是正常运行的,我们来看下面这些

  1. >>> myfunc()
  2. ['add']
  3. >>> myfunc()
  4. ['add', 'add']
  5. >>> myfunc()
  6. ['add', 'add', 'add']

按理说应该每次都是['add'],但是现在出现了意想不到的错误。

这是因为l = []是在函数定义时就确定下来的了,所以之后每次调用这个函数,使用的l都是同一个,如果不指定这个参数的新值,就会出现上面这个问题。

上面这个l可以默认设置为None,这就是一个不可变对象。

类的实现上的差异

其实list tuple里的这些元素都相当于类的属性,修改他们相当于修改类的属性。

正常定义一个类它的属性是可以正常访问和修改的,所以那些类的实例都是可变对象。

我们只要定义一个类,不允许它修改属性,就可以创建一个不可变对象。

这就要使用python的魔法方法,主要有两种方法

  • 设置__setattr__直接抛出异常,即只要想设置属性值,就会抛出异常
  • 设置__slot__限制属性的访问,如果属性都不能访问了,那就肯定不能修改

更细节的实现可以参考stackoverflow上的回答

参考

Python入门之python可变对象与不可变对象的更多相关文章

  1. Python入门之 Python内置函数

    Python入门之 Python内置函数 函数就是以功能为导向,一个函数封装一个功能,那么Python将一些常用的功能(比如len)给我们封装成了一个一个的函数,供我们使用,他们不仅效率高(底层都是用 ...

  2. Python 入门之Python基础数据类型及其方法

    Python 入门之Python基础数据类型 1. 整型:int 用于计算,用于比较 (在赋值的时候先执行等号右边的内容) 1.1 整数的加 a = 10 b = 20 print(a + b) 结果 ...

  3. Python 入门之 Python三大器 之 生成器

    Python 入门之 Python三大器 之 生成器 1.生成器 (1)什么是生成器? 核心:生成器的本质就是一个迭代器 迭代器是Python自带的 生成器程序员自己写的一种迭代器 def func( ...

  4. Python 入门之 Python三大器 之 迭代器

    Python 入门之 Python三大器 之 迭代器 1.迭代器 (1)可迭代对象: <1> 只要具有__ iter __()方法就是一个可迭代对象 (我们可以通过dir()方法去判断一个 ...

  5. Python 入门之Python简介

    Python 入门之Python简介 1.Python简介: (1) Python的出生: ​ python的创始人为吉多·范罗苏姆(Guido van Rossum)(中文名字:龟叔).1989年的 ...

  6. Python 入门之 Python三大器 之 装饰器

    Python 入门之 Python三大器 之 装饰器 1.开放封闭原则: (1)代码扩展进行开放 ​ 任何一个程序,不可能在设计之初就已经想好了所有的功能并且未来不做任何更新和修改.所以我们必须允许代 ...

  7. Python 入门之Python基础知识

    Python 入门之Python基础知识 1.变量 (1)变量就是把程序运行的中间结果临时存在内存中,以便后续代码使用 (2)变量的作用: 昵称,就是代指内存中某个地址中的内容 a = 123 变量名 ...

  8. python入门3 python变量,id(),is运算符

    python变量无需声明数据类型,可以直接赋值使用. 比如: num=100 #整数 str="字符串" #字符串 turple1 =('mon','tue','wed','thu ...

  9. boost.python入门教程 ----python 嵌入c++

    Python语言简介 Python是一种脚本语言.以开放的开发接口和独特的语法著称.尽管Python在国内引起注意只有几年的时间,但实际上Python出现于上世纪90年代(据www.python.or ...

随机推荐

  1. window.onload的一些说明

    window.onload事件对于初学者来说,经常会让我们感觉不好理解,并且经常会犯一些错误,初学js的时候经常碰到有关于它的问题,我想和我一样很多初学者也会碰到,那时候不懂它的具体作用,只要一写代码 ...

  2. 小程序中navigator和wx.navigateTo,wx.redirectTo,wx.reLaunch,wx.switchTab,wx.navigateBack的用法

    如果用一句话来表明navigator和API中wx.系列的跳转有什么区别,那就是navigator是在wxml中用标签添加open-type属性来达到和wx.系列一样的效果. navigator的属性 ...

  3. vue报错 Uncaught TypeError: Cannot read property ‘children ’ of null

    Uncaught TypeError: Cannot read property ‘children ’ of null ratings未渲染完毕,就跳走goods了,取消默认跳转,即可

  4. python----并发之协程

    <python并发之协程>一: 单线程下实现并发,即只在一个主线程,并且cpu只有一个的情况下实现并发.(并发的本质:切换+保存状态) cpu正在运行一个任务,会在两种情况下切去执行其他的 ...

  5. uoj#228. 基础数据结构练习题(线段树区间开方)

    题目链接:http://uoj.ac/problem/228 代码:(先开个坑在这个地方) #include<bits/stdc++.h> using namespace std; ; l ...

  6. Oracle管理监控之sql developer配置与简单使用

    1. 前提:使用sqldeveloper的前提是你的机器上安装了Oracle. 2. 刚打开sql developer 找到所安装的jdk的目录,本人安装的路径为:C:\Java\jdk1.6.0\b ...

  7. 网站优化(SEO)的10大误区

    前段时间大前端也有关于SEO的文章贡献给广大读者,今日,再发一文,网站优化(SEO)的10大误区.很多新手站长初次接触SEO,感受到SEO 的无穷魅力,想要做一位优秀的SEOer,然而新手朋友在进行S ...

  8. 【Pyton】【小甲鱼】类和对象:一些相关的BIF(内置函数)

    1.issubclass(class,classinfo) 1)一个类被认为是其自身的子类 2)classinfo可以使类对象组成的元祖,只要class与其中任何一个候选类的子类,则返回True. & ...

  9. vue项目中px自动转换为rem

    .安装 postcss-pxtorem : npm install postcss-pxtorem -D .修改 /build/utils.js 文件 找到 postcssLoader const p ...

  10. java读取resource目录下的配置文件

    java读取resource目录下的配置文件 1:配置resource目录 下的文件 host: 127.0.0.1 port: 9300 2:读取    / 代表resource目录 InputSt ...