python黑魔法之参数传递


Posted in Python onFebruary 12, 2016

我们都听说,python世界里面,万物皆对象。
怎么说万物皆对象呢?最常见的:

> class A: pass
> a = A()

我们说a是一个对象。
那么既然是万物了,其实A也是对象。3 也是对象。True 也是对象。"hello" 也是对象。
> def Func(): pass
o~yee, Func 也是对象。
那么对象之间的传递是如何呢?我们看看下面两个简单的例子:

> a = 3
> b = a
> b = 3 + 1
> print b
4
> print a
3

> a = []
> b = a
> b.append(1)

> print a
[1]
> print b
[1]

不是都说python所有对象都是引用传递吗?为毛第一个b不是3?
好吧。事实是,在python的实现上,对象分为mutable 和 immutable。
这里说的对象分类,是说在实现上具备这样的特性。而非对象本身的属性。
什么是immutable?表示对象本身不可改变。这里先记住一点,是对象 本身 不可改变。
什么叫做对象本身不可改变呢?
一个简单的例子:

> a = (1,2,3)
> a[0] = 10

TypeError: 'tuple' object does not support item assignment
元组的元素在初始化后就不能再被改变。也就是说,元组对象具备immutable的特性。
那么很简单,相对的,mutable 就是可变的。比如:

> a = {}
> a[0] = 10

有了上面的两个例子,相信大家已经有了基本的认识。
那么,在python世界中,哪些是具备immutable特性,哪些又是mutable的呢?
简单讲,基本类型都是immutable, 而object都是mutable的。
比如说:int, float, bool, tuple 都是immutable。
再比如:dict, set, list, classinstance 都是mutable的。
那么问题来了。既然说基本类型是 immutable ,那么最上面的 b = 3 + 1 为什么不会像tuple一样,抛异常呢?
原因在于,int 对+操作会执行自己的__add__方法。而__add__方法会返回一个新的对象。
事实是,当基本类型被改变时,并不是改变其自身,而是创建了一个新的对象。最终返回的是新的对象的引用。
怎么证明?
我们可以使用一个叫做id()的函数。该函数会返回对象的一个唯一id(目前的实现可以间接理解为对象的内存地址)。
那么我们看下:

> a = 3
> id(a)
140248135804168

> id(3)
140248135804168

> id(4)
140248135804144

> a = a + 1
> id(a)
140248135804144

you see ? 当我们执行a=a+1 后,id(a) 已经改变了。
深究一点,为什么会这样呢?
其实,a = a + 1 经历了两个过程:

  • 1、a + 1
  • 2、a 赋值

第2步只是一个引用的改变。重点在第1步。a + 1,那么python实际上会调用a.__add__(1)。
对于int类型__add__函数的实现逻辑,是创建了一个新的int对象,并返回。
不知道细心的你有没有发现一个特别的地方?
id(4)的值等于id(3+1) 。这个只是python对int,和bool做的特殊优化。不要以为其他基本类型只要值一样都会指向相同的对象。
有个特殊的例子,str。做个简单的实验:

> a = "hello"
> id(a)
4365413232
> b = "hell"
> id(b)
4365386208

> id(a[:-1])
4365410928
> id(a[:-1])
4365413760

看到了吗?虽然值相同,但是还是指向(创建)了不同的对象,尤其是最后两句,哪怕执行相同的操作,依然创建了不同的对象。
python这么傻,每次都创建新的对象?
no no no 他只是缓存了“一些”结果。我们可以再试试看:

> a = "hello"
> ret = set()
> for i in range(1000):
  ret.add(id(a[:-1]))
> print ret
{4388133312, 4388204640}

看到了吗?python还是挺聪明的。不过具体的缓存机制我没有深究过,期望有同学能分享下。
再次回到我们的主题,python中参数是如何传递的?
答案是,引用传递。
平时使用静态语言的同学(比如我),可能会用下面的例子挑战我了:

def fun(data):
  data = 3

a = 100
func(a)

print a # 100

不是尼玛引用传递吗?为毛在执行func(a)后,a 的值没有改变呢?这里犯了一个动态语言基本的错误。
data=3,语义上是动态语言的赋值语句。千万不要和C++之类的语言一个理解。
看看我们传入一个mutable 的对象:

> def func(m):
  m[3] = 100

> a = {}
> print a
{}
> func(a)
> print a
{3:100}

现在同学们知道该如何进行参数传递了吧?好嘞,进阶!
像很多语言如C++,js,swift... 一样,python 的函数声明支持默认参数:
def func(a=[]): pass
不知道什么意思?自己看书去!
我这里要说的是,如果我们的默认参数是mutable类型的对象,会有什么黑魔法产产生?
我们看看下面的函数:

def func(a=[]):
  a.append(3)
  return a

可能有同学会说了:我去!这么简单?来骗代码的吧?
但是,真的这么简单吗?我们看下下面的调用结果:

> print func()
[3]
> print func()
[3,3]
> print func()
[3,3,3]

这真的是你想要的结果吗?
No,我要的是[3],[3],[3]!
原因?好吧,我们再用下id()神奇看看:

def func(a=[]):
  print id(a)
  a.append(3)
  return a

> print func()
4365426272
[3]
> print func()
4365426272
[3, 3]
> print func()
4365426272
[3, 3, 3]

明白没?原来在python中,*默认参数不是每次执行时都创建的!*
这下你再想想,曾经嘲笑过的代码(至少我)为什么要 多此一举:

def func(a=None):
  if a is None:
    a = []

这里在顺带提一下==, is:
== : 值比较
is : 比较左右两边是否是同一个对象。 a is b ==> id(a) == id(b)
ok, let's move on!
我们都知道,在python中,不定参数我们可以这样定义:
def func(*args, **kv): pass
什么你不知道?看书去!
那args和kv到底是什么情况呢?到底是mutable 还是 immutable 呢?
再一次请出id()神器:

def func(*args):
  print id(args)


> a = [1,2]
> print id(a)
4364874816
> func(*a)
4364698832
> func(*a)
4364701496

看到了吧?实际上args也会产生一个新的对象。但是值是填入的传入参数。那么每一个item也会复制吗?
我们再看看:

def func(*args):
  print id(args[0])

> a = [1,2]
> print id(a[0])
140248135804216
> func(*a)
140248135804216

答案是,No。值会像普通list赋值一样,指向原先list(a)所引用的对象。
那么为什么会这样呢?
python的源码就是这么写的.......
最最后,还记得我说过的一句话吗?
immutable 限制的是对象本身不可变
意思就是说,对象的immtable 只是限制自身的属性能否被改变,而不会影响到其引用的对象。
看下下面的例子:

> a = [1,2]
> b = (a,3)
> b[1] = 100
TypeError: 'tuple' object does not support item assignment

> print b
([1, 2], 3)
> b[0][0] = 10
> print b
([10, 2], 3)

最最最后,我有个对象,它本身应该是 mutable 的,但是我想让他具备类似immutable的特性,可以吗?
答案是,可以模拟!
还是之前说的,immutable 限制的是其自身属性不能改变。
那么,我们的可以通过重定义(重载)属性改变函数,来模拟immutable特性。
python可以吗?O~Yee
在python的类函数中,有这样的两个函数: __setattr__ 和 __delattr__。分别会在对象属性赋值和删除时执行。
那么我们可以进行简单重载来模拟immutable:

class A:
  def __setattr__(self, name, val):
    raise TypeError("immutable object could not set attr")

以上就是为大家介绍的python黑魔法,希望对大家的学习有所帮助。

Python 相关文章推荐
Python格式化压缩后的JS文件的方法
Mar 05 Python
把MySQL表结构映射为Python中的对象的教程
Apr 07 Python
python使用range函数计算一组数和的方法
May 07 Python
python单元测试unittest实例详解
May 11 Python
详解python 发送邮件实例代码
Dec 22 Python
Python考拉兹猜想输出序列代码实践
Jul 05 Python
Python函数装饰器原理与用法详解
Aug 16 Python
Python 单例设计模式用法实例分析
Sep 23 Python
Django 实现 Websocket 广播、点对点发送消息的代码
Jun 03 Python
Keras在训练期间可视化训练误差和测试误差实例
Jun 16 Python
plt.figure()参数使用详解及运行演示
Jan 08 Python
利用For循环遍历Python字典的三种方法实例
Mar 25 Python
python实现井字棋游戏
Mar 30 #Python
python搭建微信公众平台
Feb 09 #Python
Python实例一个类背后发生了什么
Feb 09 #Python
Python中的条件判断语句基础学习教程
Feb 07 #Python
Python模拟登录验证码(代码简单)
Feb 06 #Python
Python上传package到Pypi(代码简单)
Feb 06 #Python
深入讲解Java编程中类的生命周期
Feb 05 #Python
You might like
PHP 获取MSN好友列表的代码(2009-05-14测试通过)
2009/09/09 PHP
PHP static局部静态变量和全局静态变量总结
2014/03/02 PHP
PHP网页游戏学习之Xnova(ogame)源码解读(十五)
2014/06/30 PHP
php可生成缩略图的文件上传类实例
2014/12/17 PHP
jQuery学习笔记之DOM对象和jQuery对象
2010/12/22 Javascript
jQuery:节点(插入,复制,替换,删除)操作
2013/03/04 Javascript
jquery+正则实现统一的表单验证
2015/09/20 Javascript
JavaScript保留关键字汇总
2015/12/01 Javascript
Kindeditor在线文本编辑器如何过滤HTML
2016/04/14 Javascript
Angular的Bootstrap(引导)和Compiler(编译)机制
2016/06/20 Javascript
利用Angularjs和原生JS分别实现动态效果的输入框
2016/09/01 Javascript
一步一步封装自己的HtmlHelper组件BootstrapHelper(三)
2016/09/14 Javascript
node.js用fs.rename强制重命名或移动文件夹的方法
2017/12/27 Javascript
js input输入百分号保存数据库失败的解决方法
2018/05/26 Javascript
Jquery如何使用animation动画效果改变背景色的代码
2020/07/20 jQuery
vue的hash值原理也是table切换实例代码
2020/12/14 Vue.js
Python2.5/2.6实用教程 入门基础篇
2009/11/29 Python
python中实现延时回调普通函数示例代码
2017/09/08 Python
python 利用栈和队列模拟递归的过程
2018/05/29 Python
Django中更改默认数据库为mysql的方法示例
2018/12/05 Python
Python Django Vue 项目创建过程详解
2019/07/29 Python
Pandas操作CSV文件的读写实现方法
2019/11/13 Python
python读写文件write和flush的实现方式
2020/02/21 Python
Python获取对象属性的几种方式小结
2020/03/12 Python
Python如何将装饰器定义为类
2020/07/30 Python
使用Python制作一盏 3D 花灯喜迎元宵佳节
2021/02/26 Python
美国最大的袜子制造商和零售商:Renfro Socks
2017/09/03 全球购物
PHP高级工程师面试问题推荐
2013/01/18 面试题
How to spawning asynchronous work in J2EE
2016/08/29 面试题
精选干货:Java精选笔试题附答案
2014/01/18 面试题
个人股份合作协议书
2014/10/24 职场文书
2014年秘书工作总结
2014/11/25 职场文书
幼儿园园长个人总结
2015/03/02 职场文书
教师网络培训心得体会
2016/01/09 职场文书
超详细教你怎么升级Mysql的版本
2021/05/19 MySQL
Android studio 简单计算器的编写
2022/05/20 Java/Android