注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

@fc_lamp

关注Web应用解决方案MySql/PHP/Python一盏名为"飞川"的灯~

 
 
 

日志

 
 

简单说说python中函数传值方式(引用传值,按值传递,默认参数、关键字参数)  

2012-05-29 18:05:00|  分类: Web技术-Python |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

简单说说python中函数传值方式(引用传值,按值传递,默认参数、关键字参数)

(注:在python中,一切对象皆引用,在Python中,无论你把对象做为参数传递,做为函数返回值,都是引用传递的)

eg:

import copy
a =['a','b','c']
b = a
c = copy.copy(a)
b.append('d')
print 'a=',a #a= ['a', 'b', 'c', 'd']
print 'b=',b #b= ['a', 'b', 'c', 'd']
print 'c=',c #c= ['a', 'b', 'c']

#再注意对比:

a=[1]
b = a
b = [1,2]
print a #[1]


一 引言

恩,用过PHP的人都知道PHP的函数有两种传值方式:按值传值 和 引用传值。这两种方式在PHP里是比较分的清楚的,默认情况下使用的是按值传递,如果你想要按引用传值,就得在参数前面预先加上符号"&"。

但是在python里,函数的传值方式却有些“模糊”,我这里所说的“模糊”是指会让一些初学者搞不懂究竟是怎么事(比如:俺)?

那么,这个问题我们一歩一歩的来说明。

二 变量

1 定义

“变量”!这个词对于我们编程的人员来说,再熟悉不过了。但是,这个词在不同类型的编程语言中却有不同的“含义”。为了说明这个问题,我还是摘录一段别人的解释:

Other languages have "variables" (fc_lamp注:像C等一些静态编程语言)

In many other languages, assigning to a variable puts a value intoa box.int a = 1;简单说说python中函数传值方式(引用传值,按值传递,默认参数、关键字参数) - fc-lamp - fc-lamp的博客

Box "a" now contains an integer 1.

Assigning another value to the same variable replaces the contentsof the box:

a = 2;简单说说python中函数传值方式(引用传值,按值传递,默认参数、关键字参数) - fc-lamp - fc-lamp的博客

Now box "a" contains an integer 2.

Assigning one variable to another makes a copy of the value andputs it in the new box:

int b = a;简单说说python中函数传值方式(引用传值,按值传递,默认参数、关键字参数) - fc-lamp - fc-lamp的博客简单说说python中函数传值方式(引用传值,按值传递,默认参数、关键字参数) - fc-lamp - fc-lamp的博客"b" is a second box, with a copy of integer 2. Box "a" has aseparate copy.

Python has "names"(fc_lamp注:这里还有像PHP等一些动态脚本语言)

In Python, a "name" or "identifier" is like a parcel tag (ornametag) attached to an object.a = 1简单说说python中函数传值方式(引用传值,按值传递,默认参数、关键字参数) - fc-lamp - fc-lamp的博客

Here, an integer 1 object has a tag labelled "a".

If we reassign to "a", we just move the tag to another object:

a = 2简单说说python中函数传值方式(引用传值,按值传递,默认参数、关键字参数) - fc-lamp - fc-lamp的博客简单说说python中函数传值方式(引用传值,按值传递,默认参数、关键字参数) - fc-lamp - fc-lamp的博客

Now the name "a" is attached to an integer 2 object.

The original integer 1 object no longer has a tag "a". It may liveon, but we can't get to it through the name "a". (When an objecthas no more references or tags, it is removed from memory.)(fc_lamp注:注意这句话)

If we assign one name to another, we're just attaching anothernametag to an existing object:

b = a简单说说python中函数传值方式(引用传值,按值传递,默认参数、关键字参数) - fc-lamp - fc-lamp的博客The name "b" is just a second tag bound to the same object as "a".

Although we commonly refer to "variables" even in Python (becauseit's common terminology), we really mean "names" or "identifiers".In Python, "variables" are nametags for values, not labelled boxes.


实际,这段表明了像python,PHP这类动态脚本语言中“变量”包含了两个内容:1 标识符名称 2 标识符所对应(引用)的值(对象),也就是说“变量”不在是一个容器。

我们通过例子来说明一下:


x = 3
y = 5
print 'id x:%d'%(id(x)) #输出id x:10410424
print 'id y:%d'%(id(y)) #输出id y:10410400  (关于id()的用法可参看手册)


这里整数3 对象被 x标识符所引用了,整数5对象被 y 标识符 引用了。这说明x,y引用的是不同对象。如果,我们改变一下代码呢:


x = y
print 'now id x:%d'%(id(y)) #输出now id x:10410400


我们可以看到 x,y 都指向了同一对象 5。我们还可以用"is" 运算符来判断两标识符是否引用的是同一对象。


if x is y:
    print 'ok'


 

 2 作用域

 不管是何种传值方式,都涉及到变量的作用域问题,这里我还是摘录一文章:

在 Python 程序中创建、改变或查找变量名时,都是在一个保存变量名的地方进行中,那个地方我们称之为命名空间。作用域这个术语也称之为命名空间。

具体地说,在代码中变量名被赋值(Python 中变量声明即赋值,global 声明的只是变量的使用域)的位置决定了该变量能被访问的范围。函数定义了本地作用域,而模块定义的是全局作用域。这两个作用域之前有如下关系:

每一个模块都是全局作用域。也就是说,创建于模块文件顶层的变量具有全局作用域,对于外部访问就成了一个模块 对象的属性。全局作用域的作用范围仅限于单个文件。“全局”指的是在一个文件的顶层变量名对于这个文件而言是全局的。每次对函数的调用都创建了一个新的本地作用域。Python 中也有递归,即可以调用自身,每次调用都会创建五个新的本地命名空间。赋值的变量名除非声明为全局变量,否则均为本地变量。如果需要在函数内部对模块文件顶层的变量名赋值,需要在 函数内部通过 global 语句声明该变量。所有的变量可归纳为本地、全局或者内置三种。范围分别为 def 内部,在一个模块的命名空间内部和预定义的 __builtin__ 模块提供的变量。变量名解析:LEGB 原则

如果对以上内容有所迷惑的话,请看以下总结出的几条原则。在函数命名空间中:

变量名引用分为三个作用域进行查找:首先是本地,然后是函数内(如果有的话),之后是全局,最后是内置。在默认情况下,变量名赋值会创建或者改变本地变量。全局声明将会给映射到模块文件内部的作用域的变量名赋值。Python 的变量名解析机制也称为 LEGB 法则,具体如下:
当在函数中使用未确定的变量名时,Python 搜索 4 个作用域:本地作用域(L),之后是上一层嵌套结构中 def 或 lambda 的本地作用域(E),之后是全局作用域(G),最后是内置作用域(B)。按这个查找原则,在第一处找到的地方停止。如果没有找到,Python 会报错的。 下图说明了搜索流程(由内及外):

简单说说python中函数传值方式(引用传值,按值传递,默认参数、关键字参数) - fc-lamp - fc-lamp的博客

简单的实例test.py:


x = 88#全局变量

y = 2 #全局变量

def func(z):  #z是局部变量

    global y

    #虽然在y在函数内部重新定义前是全局的,

   #但是如果函数内有相同的变量那这两个y将是完全不同的变量 了

   #所以只能通过global关键字来改变y的值

    x = 99

    y = 55

func(2)

print('X is %d , Y is %d'%(x,y)) #输出:X is 88 , Y is 55 



这里在函数调用前x,y都是全局变量。但当在函数内部遇到同名变量时,已被划分为本地变量。

特别:在函数内部定义的任意的赋值操作变量名都为本地变量:=语句,import,def(注意def 也是赋值操作,上例中将一个函数对象赋值给了func变量),参数传递。但需要注意本地改变对象时并不会把变量划分为本地变量。如: L.append(x) 并不会让L为本地变量,而L= x 却可以。


三 传值方式

有了以上说明后,我们来看几个有趣的实例:


#coding:utf-8
def test():
    x = []   #这里的x已是局部的与函数外的全局x,已不在是同一个变量(注意这里是赋值操作)
    print 'in function x id:%d'%(id(x))
    xx.append(4) #这里xx仍然是函数外的那个全局xx,是同一变量
    print 'in function xx id:%d'%(id(xx))
  
x = [1,2,3]
xx = [1,2,3] #注意这里x与xx值是相等的,但是x与xx是没有指向同一对象的(序列是可变对象)。
print 'before x id:%d'%(id(x))
print 'before xx id:%d'%(id(xx))
test()
print 'after x id:%d'%(id(x))
print 'after xx id:%d'%(id(xx))
print '---------------result--------'
print x
print xx

以上输出:

before x id:18827504
before xx id:18826184
in function x id:18825944
in function xx id:18826184
after x id:18827504
after xx id:18826184
---------------result--------
[1, 2, 3]
[1, 2, 3, 4]




例2


#coding:utf-8
def test():
    x = []#这里的x已是局部的与函数外的全局x,已不在是同一个变量(注意这里是赋值操作)
    print 'in function x id:%d'%(id(x))
    xx.append(4) #这里xx是全局变量
    print 'in function xx id:%d'%(id(xx))
    
x = [1,2,3]
xx = x #这一歩赋值操作,让xx与x 指向了同一对象,即 x is xx。
print 'before x id:%d'%(id(x))
print 'before xx id:%d'%(id(xx))
test()
print 'after x id:%d'%(id(x))
print 'after xx id:%d'%(id(xx))
print '---------------result--------'
print x
print xx

以上输出:

before x id:18827624
before xx id:18827624
in function x id:11236656
in function xx id:18827624
after x id:18827624
after xx id:18827624
---------------result--------
[1, 2, 3, 4] #注意x 变化了,是因为它与xx指向了同一对象。
[1, 2, 3, 4]





 

例3


#coding:utf-8
def test(item,x=[]):#注意这里x 是局部的与函数外的x是不同的两个变量,并此x引用的是可变对象,

                              并在函数定义时就定义此变量而不是在每次调用时再定义

    print 'append before x id:%d'%(id(x))
    x.append(item)
    print 'append after x id:%d'%(id(x))
    return x
x= [1,2,3]
print 'before x id:%d'%(id(x))
print test(5)#注意这里函数参数 x,在调用时已绑定到可变对象序列上了。
print test(6)#多次调用时,参数x不再具有赋值操作,始终操作为同一对象
print 'after x id:%d'%(id(x))
print '---------------result--------'
print x

输出:

before x id:18826184
append before x id:18827504
append after x id:18827504
[5]
append before x id:18827504
append after x id:18827504
[5, 6]
after x id:18826184
---------------result--------
[1, 2, 3]



 

例4


#coding:utf-8
def test(item,x=[]):
    print 'append before x id:%d'%(id(x))
    x.append(item)
    print 'append after x id:%d'%(id(x))
    return x
print test(1,[])  #这里每次调用函数时,都试着给参数x 赋值,虽然每次都是相同的空序列[],但是每次x引用的对象已不是同的。
print test(2,[])
print test(3,[])

 输出:

append before x id:18826304
append after x id:18826304
[1]
append before x id:12194920
append after x id:12194920
[2]
append before x id:18826064
append after x id:18826064
[3]



 

例5

对比PHP中,用官网上的例子:


<?php
function foo(&$var)
{
    $var++;
}

$a=5;
foo($a);


这里 $var变量与$a变量是指向同一内容。

不过,再看一个有趣的PHP例子(注意这个例子):


$a = 'aaa';

$b = 'bbb';

function test()

{

global $a, $b;

$b = &$a;

$a = 'Change the value of a';

$b = 'Change the value of b';

}

test ();

var_dump ( 'print a:' . $a ); #输出 string'print a:Change the value of b'(length=29)

var_dump ( 'print b:' . $b ); #输出string'print b:bbb'(length=11)



由以上可以看出:

1)   实际上python没有像PHP那样的“引用传值”,而是“按值传递”,只是这“值”(对象)是可变的还是不可变的对象。

2)   函数默认参数在函数定义是就定义了(多次调用函数时不会被重复定义)。下面这个例子可以证明:

def printf():
print unicode('这里打印','utf-8')

def test(i,lists=printf()):
print '%s '%i

test(1)
test(2)

#以上输出:
'''
这里打印
1
2
'''


参考:http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html#other-languages-have-variables

           http://magicalboy.com/python-scope-legb.html

           PHP参考:http://www.php.net/manual/en/language.references.whatdo.php





四 传参传递类型

#默认参数(其默认值可以是变量,但此变量必须之前有定义)

#但在PHP中默认值必须是常量表达式,不能是诸如变量,类成员,或者函数调用等表达式。


a = 10

def test(t = a):

    return t

a = 5

print test() #这里输出为10



 

#可变长参数(数量可变)

#最后一位参数数量不定,加上*号就可接受任意数量的参数

#数量不定参数都作为一个元组放入args变量中,然后传递给函数 

#PHP中可在函数内使用func_get_args()来获取不定数量的参数 


def test2(a,b,*args):

    print args #这里输出(3, 4, 5, 6, 7, 8)

test2(1,2,3,4,5,6,7,8)



    

#关键字参数

#这时候参数的位置与顺序就无关紧要了。


def test3(a,b,c):

    print '%s,%s,%s'%(a,b,c) #输出aa,bb,cc

test3(c='cc',b='bb',a='aa')



 

#多个不定关键字参数

#最后一个参数以**开头,可以把所有额外的关键字参数都入到一个

#字典中并将此字典传递给函数


def test4(**agrs):

    print agrs

test4(c='cc',b='bb',a='aa',d='dd',e='ee')

#输出{'a': 'aa', 'c': 'cc', 'b': 'bb', 'e': 'ee', 'd': 'dd'}



 

#可变长参数与不定关键字参数


def test5(*args,**kargs):

    print args #输出(1, 2, 3, 4, 5)

    print kargs #输出{'a': 'aa', 'c': 'cc', 'b': 'bb', 'e': 'ee', 'd': 'dd'}

test5(1,2,3,4,5,c='cc',b='bb',a='aa',d='dd',e='ee')


五 函数其它

   #闭包 函数在python中是第一类对象,可以把它们当作参数传递给其他函数,放在数据结构中,以及作为函数结果返回。

   将组成函数的语句和这些语句的执行环境打包在一起时,得到的对象称为闭包。

def test(i):
def go(n):
print locals() #使用locals()函数可打印出当前的局部变量 {'i': 1, 'n': 1}
return i+n
return go

#只创建和返回函数go(),并且隐式地携带创建go()函数时定义的外部变量的值
one = test(1)
print one #<function go at 0x011F5430>
two = test(2)

#执行真正的功能函数
print one(1) #2
print two(2) #4

#更多时候结合 lambda 使用
def test(x):
return lambda y:x*y #lambda 参数列表:运算主体

one = test(1)
print one #<function <lambda> at 0x011F53F0>
two = test(2)

print one(2) #2
print two(3) #6

另外注意:在python2中闭包只能读外部函数的变量,而不能修改。在python3中增加了一个nonlocal 关键字来实现这功能。

def test(n):
def one():
n+=1
return n
return one
t = test(2)

当我们调用时t(),就会报错UnboundLocalError: local variable 'n' referenced before assignment 


#yield 生成器对象 生成器是一个函数,它生成一个值的序列,以便在迭代中使用。 

#coding:utf-8
'''
yield 生成器对象:生成器是一个函数,它生成一个值的序列,以便在迭代中使用。
'''
def test(n):
print n
while n>0:
yield n
n-=1
return

#返回一个生成器对象
c = test(10)
#使用next()执行函数(python3中为__next__())
c.next()
'''
>>>
10
>>> c.next()
9
>>> c.next()
8
>>> c.next()
7
>>>
'''

调用next()时,生成器函数将不断执行语句,直到遇到yield语句停止,
yield语句在函数执行停止的地方生成一个结果,直到再次调用next(),然后继续执行yield之后的语句。

更多参考:手册。


#计算阶乘 n*n-1*n-2...*1

#方法一

def fact1(n):

    if n==0:

        return 1

    return n*fact1(n-1)

#方法二()

def fact2(n):

    result = 1

    for i in xrange(n,1,-1):

        result *= i

    return result

n = 6

print fact1(n)

print fact2(n)


<?php
//PHP简单无级分类(注意:在设计数据库时,为了性能的提升,可以记录其完整的路径)
$a = array(
array(
'n'=>1,
'chil'=>array(
array(
'n'=>2,
'chil'=>null
)
)
),
array(
'n'=>3,
'chil'=>array(
array(
'n'=>4,
'chil'=>null
)
)
),
array(
'n'=>5,
'chil'=>array(
array(
'n'=>6,
'chil'=>array(
array(
'n'=>7,
'chil'=>null
)
)
),
array(
'n'=>8,
'chil'=>array(
array(
'n'=>9,
'chil'=>null
)
)
)
)
),
);

/***
* @author: fc_lamp
* @blog:fc-lamp.blog.163.com
* @param $array:数组 $i:当前循环的第几层
***/
function test($array,$i=0)
{
$str = '';
foreach($array as $one)
{
$str .= str_repeat('&nbsp;',$i).$one['n'].'<br/>';
if(is_array($one['chil']))
{
$i++;
$str .=test($one['chil'],$i);
}
//一层循环完后,减一层
$i--;
}
return $str;
}
echo test($a);


  评论这张
 
阅读(6698)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017