变量的引用
从策划到设计制作,每一步都追求做到细腻,制作可持续发展的企业网站。为客户提供成都做网站、网站建设、外贸营销网站建设、网站策划、网页设计、主机域名、虚拟空间、网络营销、VI设计、 网站改版、漏洞修补等服务。为客户提供更好的一站式互联网解决方案,以客户的口碑塑造优易品牌,携手广大客户,共同发展进步。
变量和数据都是保存在内存中的
变量和数据是分开存储的
数据保存在内存中某个位置,通过地址来标记
变量保存的是数据的地址,通过地址可以找到数据在内存空间的位置
把变量保存数据地址的过程称为引用
变量的重新赋值修改的是变量中引用数据的内存地址
变量之间的赋值实际是引用的传递
函数参数的传递,本质也是引用的传递
函数的返回值本身也是引用的传递
可变和不可变类型
不可变类型,内存中的数据不允许被修改:数字类型(int,bool,float,complex,long(2,x)、字符串、元组(tuple)
可变类型,内存中的数据可以被修改:列表list、字典dict
无论是可变还是不可变数据类型,通过赋值语句,都会改变变量的引用
Hash函数只能接收不可变数据类型,字典的键也只能是不可变数据类型,字典的value值可以是任意数据类型
局部变量
1.在函数内部定义的变量就是局部变量(作用范围只能是当前函数内部)
2.在函数外部无法直接访问局部变量
3.不同的函数中可以定义同名的局部变量
4.局部变量的生命周期:从定义变量时开始,到函数运行结束
全局变量
1.在所有函数外边定义的变量就是全局变量
2.让所有函数都能访问到,可以作为函数通信的桥梁
3.一般情况下,为了和普通变量的区别,需要加上g_或gl_前缀
4.全局变量一般放在所有函数的最上面
5.在函数内部修改全局变量,必须要加上global关键字,如果不加global只是定义了一个同名的局部变量
函数的多个返回值
python函数传对象对性能有影响。在Python中,一切皆对象,Python参数传递采用的都是“传对象引用”的方式。实际上,这种方式相当于传值和传引用的一种综合。如果函数收到的是一个可变对象(比如字典或者列表)的引用,就能修改对象的原始值,相当于通过“传引用”来传递对象。如果函数收到的是一个不可变对象(比如数字、字符或者元组)的引用,就不能直接修改原始对象,相当于通过“传值’来传递对象,此时如果想改变这些变量的值,可以将这些变量申明为全局变量。
首先,Python中一切事物皆对象,变量是对对象在内存中的存储和地址的抽象。所有的变量都可以理解是内存中一个对象的“引用”,或者,也可以看似c中void*的感觉。
python中统一都是引用传递,同时要注意类型是属于对象的,而不是变量。而对象有两种,“可更改”(mutable)与“不可更改”(immutable)对象。在python中,strings, tuples, 和numbers是不可更改的对象,而list,dict等则是可以修改的对象。
当我们写下面语句时:
Python解释器其实顺序干了两件事情:
从这里可以看出strings类型是不可变量,不可变实际上指的是不会更该字符串,比如把a = '123' 变为 a ='1234' 实际上是先创建了 “1234” 再用a去指向它。
但是,像list,dict等“可更改”的变量,他们会直接再本地更改,不会进行副本拷贝。
简言之,当在 Python 中 a = sth 应该理解为给 sth 贴上了一个标签 a。当再赋值给 a 的时候,就好象把 a 这个标签从原来的 sth 上拿下来,贴到其他对象上,建立新的"引用"。
既然Python只允许引用传递,那有没有办法可以让两个变量不再指向同一内存地址呢?
copy对于一个复杂对象的子对象并不会完全复制,什么是复杂对象的子对象呢?就比如序列里的嵌套序列,字典里的嵌套序列等都是复杂对象的子对象。对于子对象,python会把它当作一个公共镜像存储起来,所有对他的复制都被当成一个引用,所以说当其中一个引用将镜像改变了之后另一个引用使用镜像的时候镜像已经被改变了。
deepcopy的时候会将复杂对象的每一层复制一个单独的个体出来。 当然其中主要的操作还是地址问题。
当一个引用传递给函数的时候,函数自动复制一份引用,这个函数里的引用和外边的引用没有半毛关系了.所以第一个例子里函数把引用指向了一个不可变对象,当函数返回的时候,外面的引用没半毛感觉.而第二个例子就不一样了,函数内的引用指向的是可变对象,对它的操作就和定位了指针地址一样,在内存里进行修改.
引用计数
PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少.引用计数为0时,该对象生命就结束了。
优点:
缺点:
python的函数参数传递是"引用传递(地址传递)"。
python中赋值语句的过程(x = 1):先申请一段内存分配给一个整型对象来存储数据1,然后让变量x去指向这个对象,实际上就是指向这段内存(这里有点和C语言中的指针类似)。
在Python中,会为每个层次生成一个符号表,里层能调用外层中的变量,而外层不能调用里层中的变量,并且当外层和里层有同名变量时,外层变量会被里层变量屏蔽掉。函数 调用 会为函数局部变量生成一个新的符号表。
局部变量:作用于该函数内部,一旦函数执行完成,该变量就被回收。
全局变量:它是在函数外部定义的,作用域是整个文件。全局变量可以直接在函数里面应用,但是如果要在函数内部改变全局变量,必须使用global关键字进行声明。
注意 :默认值在函数 定义 作用域被解析
在定义函数时,就已经执行力它的局部变量
python中不可变类型是共享内存地址的:把相同的两个不可变类型数据赋给两个不同变量a,b,a,b在内存中的地址是一样的。
如果你用C给Matlab写过MEX程序,那么这个问题是很容易理解的(好像每次讨论Python问题时我总是把Matlab搬了出来…… 《在Matlab中把struct当成Python中的Dictionary使用》《Matlab和Python的几种数据类型的比较》)。
既然提到了MEX,就简单说一下:
一个Matlab可能形如
function ret=add3(a,b,c)
如果在C的层面实现这个函数,就会看到另一种景象:
void mexFunction(int nlhs,mxArray * plhs[],int nrhs,const mxArray * prhs[])
a,b,c三个参数的地址放在一个指针数组里,然后把这个指针数组的首地址作为参数prhs传递给函数,这说明Matlab函数的参数是传递指针的,而不是值传递。
纵然是传递的指针,但是却不能在函数里改变实参的值,因为标记为“const”了。
Python是开放源码的,我没有看。所以下面很多东西是猜的。
Python在函数的参数传递时用的什么手法?实验一下(使用ActivePython2.5):
首先介绍一个重要的函数:
help(id)
Help on built-in function id in module __builtin__:
id(...)
id(object) - integer
Return the identity of an object. This is guaranteed to be unique among
simultaneously existing objects. (Hint: it's the object's memory address.)
看最后括号里那句:Hint:it's the object's address.(它是对象的地址)
有了这个函数,下面的事情就方便多了。
a=0
id(a)
3630228
a=1
id(a)
3630216
可以看出,给a赋一次值,a的address就改变了。在C的层面看,(也许真实情况不是下面的样子,但作为一个类比应该还是可以的):
void * pa;
pa=malloc(sizeof(int));
*(int *)pa=0;
free(pa);
pa=malloc(sizeof(int));
*(int *)pa=1;
Python中每次赋值会改变变量的address,分配新的内存空间,所以Python中对于类型不像C那样严格要求。
下面看看Python函数参数传递时到底传的什么:
有一个函数:
def changeA(a):
... print id(a)
... a=100
... print id(a)
设定一个变量var1:
var1=10
id(var1)
3630108
changeA(var1)
3630108
3631012
var1
10
调用函数后,从两次print的结果可以看出,传递确实是地址。但是即便如此,在函数内对形参的修改不会对实参造成任何实质的影响,因为对形参的重新赋值,只是改变了形参所指向的内存单元(changeA里两次调用print id(a)得到不同的结果),却没有改变实参的指向。在C的层面看也许类似下面的情节:
void changeA(void * pa)
{
pa=malloc(sizeof(int));
*(int *)pa=100;
free(pa);
}
精通C的你一眼就看出这个函数永远也改变不了它外面的世界。
也就是说虽然传递的是地址,但像changeA这样的函数改变不了实参的值。
也许会感到困扰?不,我已经在Matlab中习惯了。
一个最典型的例子就是Matlab中删除结构体成员的rmfield函数(参见《Matlab笔记三则》),
(Matlab版本7.0.1)
如果想删除结构体patient的name成员,用
rmfield(patient, 'name');
是永远达不到目的的(就像试图用双手抓住自己的领子,把自己提到空中);
迷途知返的做法是:
patient = rmfield(patient, 'name');
Python函数的参数类型主要包括必选参数、可选参数、可变参数、位置参数和关键字参数,本文介绍一下他们的定义以及可变数据类型参数传递需要注意的地方。
必选参数(Required arguments)是必须输入的参数,比如下面的代码,必须输入2个参数,否则就会报错:
其实上面例子中的参数 num1和num2也属于关键字参数,比如可以通过如下方式调用:
执行结果:
可选参数(Optional arguments)可以不用传入函数,有一个默认值,如果没有传入会使用默认值,不会报错。
位置参数(positional arguments)根据其在函数定义中的位置调用,下面是pow()函数的帮助信息:
x,y,z三个参数的的顺序是固定的,并且不能使用关键字:
输出:
在上面的pow()函数帮助信息中可以看到位置参数后面加了一个反斜杠 / ,这是python内置函数的语法定义,Python开发人员不能在python3.8版本之前的代码中使用此语法。但python3.0到3.7版本可以使用如下方式定义位置参数:
星号前面的参数为位置参数或者关键字参数,星号后面是强制关键字参数,具体介绍见强制关键字参数。
python3.8版本引入了强制位置参数(Positional-Only Parameters),也就是我们可以使用反斜杠 / 语法来定义位置参数了,可以写成如下形式:
来看下面的例子:
python3.8运行:
不能使用关键字参数形式赋值了。
可变参数 (varargs argument) 就是传入的参数个数是可变的,可以是0-n个,使用星号( * )将输入参数自动组装为一个元组(tuple):
执行结果:
关键字参数(keyword argument)允许将任意个含参数名的参数导入到python函数中,使用双星号( ** ),在函数内部自动组装为一个字典。
执行结果:
上面介绍的参数可以混合使用:
结果:
注意:由于传入的参数个数不定,所以当与普通参数一同使用时,必须把带星号的参数放在最后。
强制关键字参数(Keyword-Only Arguments)是python3引入的特性,可参考:。 使用一个星号隔开:
在位置参数一节介绍过星号前面的参数可以是位置参数和关键字参数。星号后面的参数都是强制关键字参数,必须以指定参数名的方式传参,如果强制关键字参数没有设置默认参数,调用函数时必须传参。
执行结果:
也可以在可变参数后面命名关键字参数,这样就不需要星号分隔符了:
执行结果:
在Python对象及内存管理机制中介绍了python中的参数传递属于对象的 引用传递 (pass by object reference),在编写函数的时候需要特别注意。
先来看个例子:
执行结果:
l1 和 l2指向相同的地址,由于列表可变,l1改变时,l2也跟着变了。
接着看下面的例子:
结果:
l1没有变化!为什么不是[1, 2, 3, 4]呢?
l = l + [4]表示创建一个“末尾加入元素 4“的新列表,并让 l 指向这个新的对象,l1没有进行任何操作,因此 l1 的值不变。如果要改变l1的值,需要加一个返回值:
结果:
下面的代码执行结果又是什么呢?
执行结果:
和第一个例子一样,l1 和 l2指向相同的地址,所以会一起改变。这个问题怎么解决呢?
可以使用下面的方式:
也可以使用浅拷贝或者深度拷贝,具体使用方法可参考Python对象及内存管理机制。这个问题在Python编程时需要特别注意。
本文主要介绍了python函数的几种参数类型:必选参数、可选参数、可变参数、位置参数、强制位置参数、关键字参数、强制关键字参数,注意他们不是完全独立的,比如必选参数、可选参数也可以是关键字参数,位置参数可以是必选参数或者可选参数。
另外,python中的参数传递属于对象的 引用传递 ,在对可变数据类型进行参数传递时需要特别注意,如有必要,使用python的拷贝方法。
参考文档:
--THE END--