本文分为如下几个部分

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

概念

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

python内置的一些类型中

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

举一个例子

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

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

地址问题

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

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

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

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

1.可变对象

>>> a = [1, 2, 3]
>>> id(a)
2139167246856
>>> b = a
>>> id(b)
2139167246856
>>> a[1] = 4
>>> a
[1, 4, 3]
>>> b
[1, 4, 3]
>>> id(a)
2139167246856
>>> id(b)
2139167246856

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

2.不可变对象

>>> a = (1, 2, 3)
>>> id(a)
2139167074776
>>> b = a
>>> a = (4, 5, 6)
>>> a
(4, 5, 6)
>>> b
(1, 2, 3)
>>> id(a)
2139167075928
>>> id(b)
2139167074776

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

作为函数参数

1.可变对象

>>> def myfunc(l):
... l.append(1)
... print(l)
...
>>> l = [1, 2, 3]
>>> myfunc(l)
[1, 2, 3, 1]
>>> l
[1, 2, 3, 1]

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

2.不可变对象

>>> def myfunc(a):
... a += 1
... print(a)
...
>>> a = 2
>>> myfunc(a)
3
>>> a
2

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

3.总结

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

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

可变参数在类中使用

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

class Myclass:
def __init__(self, a):
self.a = a
def printa(self):
print(self.a)

运行如下

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

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

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

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

import time
import threading
import random
from queue import Queue class Producer(threading.Thread): def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue def run(self):
while True:
random_integer = random.randint(0, 100)
self.queue.put(random_integer)
print('add {}'.format(random_integer))
time.sleep(random.random()) class Consumer(threading.Thread): def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue def run(self):
while True:
get_integer = self.queue.get()
print('lose {}'.format(get_integer))
time.sleep(random.random()) def main():
queue = Queue()
th1 = Producer(queue)
th2 = Consumer(queue)
th1.start()
th2.start() if __name__ == '__main__':
main()

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

函数默认参数

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

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

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

>>> myfunc()
['add']
>>> myfunc()
['add', 'add']
>>> myfunc()
['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. ActiveMQ延迟消息配置

    ActiveMQ使用延迟消息,需要在activemq.xml配置文件中添加这项: schedulerSupport="true" <broker xmlns="ht ...

  2. JavaScript之prototype对象

    简述prototype: 在js中,每个构造函数都有一个原型属性prototype,因为这个属性的值通常是一个对象,又叫原型对象!你不需要显式的去定义原型对象,因为每个构造函数都会一个原型属性,通常在 ...

  3. NGINX域名跳转案列

    1.不同域名不同路径跳转 nginx实现a.com/teacher域名跳转到b.com/student 若想实现上面题目的跳转,目前鄙人知道两种方式: 1.return 2.proxy_pass 具体 ...

  4. js遍历json对象

    原生js遍历json对象 遍历json对象: 无规律: <script> var json = [ {dd:'SB',AA:'东东',re1:123}, {cccc:'dd',lk:'1q ...

  5. windows乱码

    对于支持 UNICODE的应用程序,Windows 会默认使用 Unicode编码.对于不支持Unicode的应用程序Windows 会采用 ANSI编码 (也就是各个国家自己制定的标准编码方式,如对 ...

  6. Python中各种括号的区别、用途及使用方法

    python语言最常见的括号有三种,分别是:小括号( ).中括号[ ]和大括号也叫做花括号{ }.其作用也各不相同,分别用来代表不同的python基本内置数据类型. python中的小括号( ):代表 ...

  7. Docker基本命令与使用 —— Docker容器(一)

    一.容器的基本操作 1. 启动容器 docker run IMAGE [COMMAND] [ARG...] run 在新容器中执行命令 eg: docker run ubuntu echo 'Hell ...

  8. java---rce

    http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-app ...

  9. flask中cookie,session的存储,调用,删除 方法(代码demo)

    # -*- encoding: utf-8 -*- # cookie,session的存储,调用,删除 from flask import Flask,make_response,request,se ...

  10. windows底下怎么让cmder通过输入subl去打开sublime text

    在window自身的cmd或者你安装的cmder中输入 doskey subl="D:\Program Files\sublime3\Sublime Text 3\sublime_text. ...