WEB开发网
开发学院软件开发Python 循序渐进学Python之函数入门 阅读

循序渐进学Python之函数入门

 2008-09-02 12:48:08 来源:WEB开发网   
核心提示:函数是Python编程的核心内容之一,本文首先为读者介绍什么是函数,循序渐进学Python之函数入门,然后详细介绍函数的定义和调用方法,最后,实际上,这些只是与函数有关的知识点的一部分,我们通过大量实例代码详细介绍了变量的作用域以及与此有关的global语句,一、什么是函数很多时候

函数是Python编程的核心内容之一,本文首先为读者介绍什么是函数,然后详细介绍函数的定义和调用方法,最后,我们通过大量实例代码详细介绍了变量的作用域以及与此有关的global语句。

一、什么是函数

很多时候,Python程序中的语句都会组织成函数的形式。通俗地说,函数就是完成特定功能的一个语句组,这组语句可以作为一个单位使用,并且给它取一个名字,这样,我们就可以通过函数名在程序的不同地方多次执行(这通常叫做函数调用),却不需要在所有地方都重复编写这些语句。另外,每次使用函数时可以提供不同的参数作为输入,以便对不同的数据进行处理;函数处理后,还可以将相应的结果反馈给我们。

有些函数是用户自己编写的,通常我们称之为自定义函数;此外,系统也自带了一些函数,还有一些第三方编写的函数,如其他程序员编写的一些函数,我们称为预定义的Python函数,对于这些现成的函数用户可以直接拿来使用。

二、为什么使用函数

我们之所以使用函数,主要是出于两个方面的考虑:一是为了降低编程的难度,通常将一个复杂的大问题分解成一系列更简单的小问题,然后将小问题继续划分成更小的问题,当问题细化为足够简单时,我们就可以分而治之。这时,我们可以使用函数来处理特定的问题,各个小问题解决了,大问题也就迎刃而解了。二是代码重用。我们定义的函数可以在一个程序的多个位置使用,也可以用于多个程序。此外,我们还可以把函数放到一个模块中供其他程序员使用,同时,我们也可以使用其他程序员定义的函数。这就避免了重复劳动,提供了工作效率。

三、函数的定义和调用

当我们自己定义一个函数时,通常使用def语句,其语法形式如下所示:

def 函数名(参数列表):
  函数体

其中,函数名可以是任何有效的Python标识符;参数列表是调用该函数时传递给它的值,可以由多个、一个或零个参数组成,当有多个参数时各个参数由逗号分隔;圆括号是必不可少的,即使没有参数也不能没有它;函数体是函数每次被调用时执行的代码,可以由一个语句或多个语句组成,函数体一定要注意缩进。此外,初学者经常忘记圆括号后面的冒号,这会导致语法错误。

这里介绍一下形式参数和实际参数,在定义函数时函数名后面圆括号中的变量名称叫做“形式参数”,或简称为“形参”;在调用函数时,函数名后面圆括号中的变量名称叫做“实际参数”,或简称为“实参”。

请看下面的函数定义,这里定义的函数将传给它的数值增1,然后将增加后的值返回给调用者:

def add1(x):
x = x + 1
return x

其中,return语句的作用是结束函数调用,并将结果返回给调用者。不过,对于函数来说,该语句是可选的,并且可以出现在函数体的任意位置;如果没有return语句,那么该函数就在函数体结束位置将控制权返回给调用方,这时相当于其他编程语言中的“过程”。在本例中,return语句是将变量x的值传递给调用者。我们在交互方式下测试该函数,如下图所示:

循序渐进学Python之函数入门

图1 函数举例

调用函数的一般形式是:

函数名(参数表)

在图1中,我们通过

add1(1)

来调用函数add1()。其中,整数1作为参数传递给该函数,函数将整数1加1,并将得到的结果返回给我们,所以Python解释器显示的数字为2。对于没有使用return语句的函数,它实际上也向调用者返回一个值,那就是None,如下所示:

循序渐进学Python之函数入门

图2 无return语句的函数的“返回值”

注意,上面的函数调用方式是一种标准调用方式,传递的值按照形参定义的顺序相应地赋给它们。除此之外,还有一种称为“关键字调用”方式,即在调用函数时同时给出形式参数和实际参数。“关键字调用”方式在函数具有多个参数是非常有用,因为解释器能通过给出的关键字来匹配参数的值,所以这样就允许参数缺失或者不按定义函数时的形式参数的顺序提供实际参数。

现在举一个例子,假设我们有一个函数叫做select(),它用来选择学校中哪个年级的哪个班去打扫卫生,我们给它定义了两个参数,第一个参数是x,表示年级,第二个参数是y,表示班级。其定义的伪码如下所示:

def select(x, y):
让x年级y班的学生打扫卫生

如果我们想要3年级6班的学生去打扫卫生,可以按照函数声明中参数的定义顺序,输入相应的参数,就可以调用这个函数了,如下所示:

select(3, 6)

表示年级的参数x得到整数3,而表示年纪的参数y得到整数6。如果我们将上面的实际参数的顺序换一下,如下所示:

select(6, 3)

那么它就表示让6年级3班的学生去打扫卫生了。

上面使用的标准方式来调用函数。下面我们再使用“关键字调用”方式来调用同一个函数。我们还是让3年级6班的学生去打扫卫生,这时的调用形式如下所示:

select(x=3, y=6)

当然,我们也可以不按照函数声明中的参数顺序输入,如下所示:

select(y=6,x=3 )

这同样是让3年级6班的学生去打扫卫生,而不是6年级3班的学生,因为这里我们显式地将形参和实参联系到了一起。此外,如果函数使用了缺省参数的话,我们可以省略某些实参,并通过关键字参数来指出提供的实参是赋给那些形参的。

四、缺省参数

在定义函数时,我们可以用赋值符号给某些形参指定默认值,这样当调用该函数的时候,如果调用方没有为该参数提供值的话,则使用默认值;如果调用该函数的时候为该参数提供了值的话,则使用调用方提供的值——像这样的参数我们称之为缺省参数。

需要注意的是默认参数在形式参数表中的位置,即默认参数必须在所有标准参数之后定义,例如:

def f(arg1,arg2=2,arg3=3):
print 'arg1 = ', arg1
print 'arg2 = ', arg2
print 'arg3 = ', arg3

这里,我们给后来个参数arg2和arg3指定了默认值,函数体的作用是输出该函数三个参数的值。在交互式环境下执行该函数的情况如下所示:

循序渐进学Python之函数入门

图3 带有缺省参数的函数

现在我们对上述实验做一些解释。首先,我们看一下通过f(10)进行函数调用时的情形,因为arg1没有缺省值,所以必须为它提供实参,所以f(10)中的实参10将传递给形参arg1,由于没有给缺省参数arg2和arg3传递实参,所以它们采用默认值2和3。

然后,我们又用了f(10,10)来调用函数,这次第一个实参按顺序给arg1,第二个实参按顺序传给arg2,所以这时缺省参数的值将是10,而非缺省值2。对于arg3,由于没有给它传递实参,所以它的值依旧为缺省值3。

最后,我们又用了f(10,10,10)来调用函数,这次传递了三个实参,所以它们的值都会变成10,包括后两个缺省参数。

上面我们利用标准调用方式调用了缺省参数的函数,下面我们看看对同一个函数使用“关键字调用”方式的情形,如下图所示:

循序渐进学Python之函数入门

图4 用“关键字调用”方式调用带有缺省参数的函数

使用缺省参数的好处是,如果某个参数大部分情况下都取某个固定的值,那么就可以为这个参数定义一个默认值,这样在以后使用这个函数时带来很大的便利,因为我们大部分时间都不用给它传参数;如果偶尔情况有变,还可以给它传递更适合的值——真是一举两得呀!

五、局部变量和全局变量

在Python中的任何变量都有其特定的作用域,比如在一个函数中定义的变量一般只能在该函数内部使用,这些只能在程序的特定部分使用的变量我们称之为局部变量;比如在一个文件顶部定义的变量可以供该文件中的任何函数调用,这些可以为整个程序所使用的变量称为全局变量。

上面是从空间的角度来考察变量的局部性和全局性,如果从时间的角度来看,不妨简单地认为在程序运行的整个过程中,全局变量一直占据着内存,并且它的值可以供所有函数访问;而局部变量则是只有在其所在函数被调用时才给它分配内存,当函数返回时,其所占内存就会被释放,所以它只能供其所在的函数所访问——换句话说,当某个函数退出时,其局部变量原先所占的内存将被分配给其它函数的局部变量。打个不太贴切的比方,全局变量就好比您自己买的房子,通常一家人要在那里住上好几十年,如果我要找您要债,我会老往您家里跑;而局部变量好像旅馆,今天您租住这件房间,我可以到这个房间来找您,但是到明天再来这间房间的话,找到的可能就是别人了。

下面提供一个例子来了解局部变量和全局变量,代码如下所示:

# -*- coding: cp936 -*-
#定义全局变量
globalInt = 9
#定义一个函数
def myAdd():
  localInt = 3   #在函数中定义一个局部变量
  return globalInt + localInt
#测试变量的局部性和全局性
print myAdd()
print globalInt
print localInt

上述代码中,我们定义了一个全局变量globalInt,该变量将在整个程序中有效;然后定义了一个函数myAdd(),并在这个函数中定义了一个局部变量localInt,该局部变量只能在函数myAdd()中有效。代码的最后部分,我们先打印了函数的返回值,然后分别打印全局变量globalInt和局部变量localInt的值。示例代码在IDLE中执行的结果如下所示:

循序渐进学Python之函数入门

图5 局部变量和全局变量用法举例

代码的执行结果表明,函数myAdd()既可以访问其内部自己定义的局部变量localInt,也可以使用在其外部定义的全局变量globalInt,所以调用该函数时返回的结果为12。然后打印了全局变量globalInt的值,其为9,这说明它全局可用。最后打印局部变量localInt时遇到了错误,错误提示说“localInt”没有定义,这是因为局部变量localInt只能在定义它的函数myAdd()中有效,或者说可见,而超出函数范围之外的代码是看不到它的,根据先定义后使用的原则,Python解释器会认为该程序使用了未定义的变量名。

读者可能会问:如果我们定义了一个全局变量,然后又在函数中用同样的名字定义了一个局部变量,换句话说,当局部变量和全局变量名称发生冲突时,函数是将这个名称作为全局变量对待呢还是作为局部变量对待?比如下列代码:

# -*- coding: cp936 -*-
#定义全局变量
g = 9
#定义一个函数
def myf():
  g = 3   #定义一个局部变量,并且与前面定义的全局变量重名
  print 'g =', g
#测试变量g的局部性和全局性
myf()

上述代码在IDLE中执行的结果如下所示:

循序渐进学Python之函数入门

图6 局部变量和全局变量的困惑

乍一看,变量g在函数myf()中的好像是作为局部变量使用,因为我们在这个函数中定义了局部变量g,并将变量g初始化为3,调用函数的结果显示变量g的值为3,如上图所示。但是仔细一想,事情好像又不是这样,因为上面提到,全局变量在整个程序中有效,所以函数myf()中的代码

g = 3   #定义一个局部变量,并且与前面定义的全局变量重名

不仅可以理解为定义并初始化局部变量g,同时还可以看作是给全局变量g重新赋值。如果按照后一种解释的话,结果也是一样的。读者可能被我搞糊涂了,不要紧,我们再做一个实验就会弄清真相了,我们对上述代码稍作修改,如下所示:

# -*- coding: cp936 -*-
#定义全局变量
g = 9
#定义一个函数
def myf():
  print 'g =', g
  g = 3  
  
#测试变量的局部性和全局性
myf()

这一次,我们首先在函数myf()中输出变量g的值,如果调用函数时打印的g的值为9的话,说明我们上面的第二种推测是正确的,否则前一个推测是正确的。下面看看运行结果:

循序渐进学Python之函数入门图7 变量g在函数中被作为局部变量

事实表明,在同一个源文件中,全局变量和局部变量同名时,在局部变量的作用范围内,全局变量不起作用。因为要是在函数myf()中变量g作为全局变量的话,这里肯定会输出它的值,即9,然而事实并非如此,Python解释器在最后一行,即

UnboundLocalError: local variable 'g' referenced before assignment

明确指出,变量g是一个局部变量。之所以出现这样的错误,是因为当Python在检索一个变量名时,它首先在函数范围内寻找,如果在函数中没有找到那个变量名,它才会到全局范围内寻找。但是这里在函数中找到了变量g的名字,但是我们在赋值前就引用了它,所以才导致了上面所示的UnboundLocalError异常。如果我们将上面的代码再改一下,如下所示:

  # -*- coding: cp936 -*-
#定义全局变量
g = 9
#定义一个函数
def myf():
  print 'g =', g
  #g = 3  
  
#测试变量的局部性和全局性
myf()

这里我们只是将函数myf()中的

g = 3

一句给注释掉,就是让它不起作用,其余部分一点也没有改动。并且再次执行该程序,结果如下:

循序渐进学Python之函数入门

图8 变量g在函数中被作为局部变量

这一次,Python在执行函数myf()时,由于在该函数中没有找到变量g的定义(注意,与C语言不同,Python在定义变量时不用在变量类型,所以有时给变量赋值和定义变量很难区分),所以到函数之外的地方查找它的定义。由于我们在程序的开头部分有变量g的定义,并将其初始化为9,所以函数将变量g作为全局变量。

六、global语句

从上面的介绍中我们可以看到,当函数内部的变量跟外部的变量同名时,对于该函数来说,有时候该变量是全局变量,有时候该变量是局部变量,现在总结如下:

◆如果在函数内部对该变量进行了赋值操作,无论是在引用该变量之前还是在引用该变量之后,那么对于该函数来说,此变量是一个局部变量。

◆如果在函数内部从未对该变量进行赋值操作,那么对于该函数来说,此变量是一个全局变量。

读者可能会问:这是不是说我们无法在函数内部给一个全局变量赋值呢?比如,在程序中定义了一个全局变量a,如果在函数f()中给变量a赋值的话,变量a就会被Python当成局部变量对待,所以全局变量的值还是没变。示例代码如下所示:

# -*- coding: cp936 -*-
#定义全局变量
a = 'hello'
print '全局变量a =', a
#定义一个函数
def f():
  a = 'byby'
  print '函数f()中的变量a =',a
  
#测试变量的局部性和全局性
f()
print '全局变量a =', a

上述代码的运行情况如下所示:

循序渐进学Python之函数入门

图9 尝试在函数中给全局变量赋值失败

从上图可以看到,调用函数之前,变量a(全局变量)的值为字符串“hello”,调用函数时变量a(局部变量)的值为字符串“byby”,调用函数之后变量a(全局变量)的值仍然还是字符串“hello”。结果很明显,要想在函数中给全局变量赋值还得另外想办法。事实上,我们可以通过global语句来达到这个目的。global语句的作用是将某些变量声明为全局变量,它的语法形式如下所示:

global 变量名

注意,这里的变量名可以是一个,也可以使多个。当关键词global后面跟随多个变量名称时,各名称之间要用逗号分隔开来。

当我们在函数中将某个变量声明为全局变量(即告诉Python解释器该变量的定义在函数之外)后,即使给该变量赋值,Python解释器也不会将它作为局部变量处理了。我们在上面的代码中加入global语句,将变量a声明为全局变量,看看效果如何:

# -*- coding: cp936 -*-
#定义全局变量
a = 'hello'
print '全局变量a =', a
#定义一个函数
def f():
  global a  #将变量a声明为全局变变量
  a = 'byby'
  print '函数f()中的变量a =',a
   
#测试变量的局部性和全局性
f()
print '全局变量a =', a

上述代码的运行情况如下所示:

循序渐进学Python之函数入门

图10 成功的在函数中给全局变量赋值

从上图可以看到,调用函数之前,全局变量a的值为字符串“hello”,因为在函数中将变量a声明为全局变量,所以从调用函数时将变量a赋值为“byby”,调用函数之后,变量a的值就从此改为字符串“byby”。

现在将在函数中与全局变量重名的变量的作用域做一个更加全面的总结:

◆如果该变量没有用global语句声明为全局变量,那么

◆如果在函数内部对该变量进行了赋值操作,无论是在引用该变量之前还是在引用该变量之后,那么对于该函数来说,此变量是一个局部变量。

◆如果在函数内部从未对该变量进行赋值操作,那么对于该函数来说,此变量是一个全局变量。

◆如果该变量用global语句声明为全局变量,那么无论是否对其进行了赋值操作,该变量都将作为全局变量。

七、小结

在本文中,我们为读者介绍了什么是函数,并详细介绍函数的定义和调用方法。之后,我们通过大量实例代码详细介绍了变量的作用域以及与此有关的global语句。实际上,这些只是与函数有关的知识点的一部分,更多的知识点将在后续的文章中陆续加以介绍。

Tags:循序渐进 Python 函数

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接