Python函数

2015/04/07 Python

Python函数

和所有的编程语言一样,Python的函数既可以有参数,也可以无参数;既可以有返回值,也可以无返回值。关键字def用来定义一个函数,使用关键字return返回函数结果(如果需要)。

定义函数

函数可分为内建函数(built-in)与用户自定义函数(user-defined)。Python本身就内建了许多函数,比如之前使用过的help()、round()、len()都是Python内建的函数,可以直接调用。另外,还有更多用途广泛的函数都放在标准库(Standard Library)或第三方开发模块库中,使用它们之前必须在程序中先加载模块库,而后就可以调用了。所谓模块(Module),是指具有特定功能的函数的组合。

至于用户自定义函数,需要先定义函数,然后才能调用。Python定义函数是使用关键词“def”,其后空一格,后接函数名称,再串接一对小括号,小括号中可以填入传入函数的参数,小括号之后再加上“:”,格式如下:

def 函数名称(参数1, 参数2, …):
    程序语句区块
    return 返回值    #有返回值时才需要

函数的程序语句区块必须缩排,函数也可以无参数,如果定义了参数,调用函数时必须传入所需的参数。也就是说,定义函数时要有“形式参数”(Formal Parameter)来准备接收数据,而调用函数要有“实际参数”(Actual Arguments)来进行数据的传递。

  • 形式参数:定义函数时,用来接收实际参数所传递的数据,进入函数主体参与指令的执行或运算。
  • 实际参数:在程序中调用函数时,将数据传递给自定义函数。

在函数执行结束后,有返回结果(return value)时,就作为函数的返回值返回给调用者;没有返回值时,函数会自动返回None对象。

例如,下面的函数有返回值(参考范例程序func.py):

def func(a,b):
    x = a + b
    return x

print(func(1,2))

程序的执行结果为3。

如果没有返回值,就会返回None,例如:

def func(a,b):
    x = a + b
    print(x)

print(func(1,2))

调用函数

声明函数之后,编译程序时就会产生与函数同名的对象,调用函数时只要使用括号“()”运算符就可以了:

函数名称(参数1, 参数2, …)

Python函数的参数分为位置参数(Positional Argument)与关键字参数(Keyword Argument),下面分别进行介绍。

位置参数

位置参数就是按照参数的位置传入参数,如果函数定义了3个参数,调用时就要带入3个参数(和函数定义的参数对应),或者采用默认参数的方式,当没有提供实际参数时,以“默认参数=值”作为参数传入,

例如(参考范例程序callFunc.py):

def func(a,b,c=0):
    x = a + b + c
    return x

print(func(1,2,3))        #输出6
print(func(1,2))          #输出3

在上面的func函数中,参数c的默认值为0,因此调用函数时可以只带入2个参数。

调用函数时,如果不想按序一对一地传递参数,就可以使用关键字参数。

关键字参数

关键字参数就是通过关键字来传入参数,只要所需的参数都指定了,调用函数时关键字参数的位置并不一定要按照函数定义时参数的顺序,

例如:

def func(a,b,c):
    x = a + b + c
    return x

print(func(c=2,b=3,a=1))  #输出6

以下调用具有相同的效果:

func(1, 2, 3)
func(a=1, b=2 , c=3)
func(1, c=3 , b=2)

如果位置参数与关键字参数混用,要特别注意以下两点:

  • 位置参数必须在关键字参数之前,否则程序语句会显示“SyntaxError:positional argument follows keyword argument”的错误信息,意思是“语法错误:位置参数跟在关键字参数后面了”:
    func(a=1, 2 , c=3)
    
  • 每个参数只能对应一个函数定义时所定义的参数,例如:
    func(1, a=2 , c=3)
    

    上面的程序语句的第一个位置参数传入给参数a,第2个参数又把数值2传递给参数a,因而Python解释器会显示“TypeError:func()got multiple values for argument’a’”的错误信息,意思是“输入错误:函数有多个值对应参数a”。

如果事先不知道要传入的参数有几个,那么可以在定义函数时在参数前面加上一个星号“*”,表示该函数可以接收不确定个数的参数,传入的参数会视为一组元组(tuple);若定义参数时前面加上2个星号“**”,则传入的参数会视为一组字典(dict)。

调用函数——传入不确定个数的参数

# 如果事先不知道要传入的参数个数,可以在定义函数时在参数前面加上一个星号“*”,表示该参数接收不确定个数的参数,传入的参数会视为一组元组。
def func(*num):
    total=0
    for n in num:
        total += n
    return total

print(func(1, 2))
print(func(1, 2, 3))
print(func(1, 2, 3, 4))

# 参数前面加上两个星号“**”,传入的参数会视为一组字典。
def func(**num):
    return num

print(func(a=1, b=2, c=3))

# 输出结果:
# 3
# 6
# 10
# {'a': 1, 'b': 2, 'c': 3}

函数的返回值

具有返回值的函数,在函数体内可以包含一个以上的return语句,程序执行到return语句就终止,然后将值返回,

可参考以下程序语句:

def func(x):
    if x < 10:
        return x
    else:
        return "Over"

a = func(15)
print(a)                          #输出Over
print(type(a))          #输出<class 'str'>

Python的函数也可以一次返回多个值,只要以逗号“,”分隔返回值即可

def func(a,b):
    n = a + b
    x = a * b
    return n, x

num1 ,num2 = func(10, 20)
print(num1)  #输出30
print(num2)  #输出200

Python的参数传递机制

以下是程序设计语言常见的两种参数传递方式。

  • 传值(Call by value)调用:表示在调用函数时,会将实际参数的值逐一复制给函数的形式参数,在函数中对形式参数的值做任何修改,都不会影响原来实际参数的值。
  • 传址(Pass by reference)调用:传址调用表示在调用函数时,传递给函数的形式参数值是实际参数的内存地址,如此一来,调用函数时的实际参数将与函数中的形式参数共享同一个内存地址,因此对形式参数值的变动连带着也会影响原来的实际参数的值。

但是Python的参数传递是使用不可变对象和可变对象来工作的:

  • 使用不可变对象(Immutable Object,如数值、字符串)传递参数时,接近于“传值”调用方式。
  • 使用可变对象(Mutable Object,如列表)传递参数时,按“传址”调用方式处理。简单来说,如果可变对象的内容或值被修改了,因为占用的是同一个地址,所以会连动影响函数外部的值(实际参数的值)。

以下范例程序用来说明在函数内部修改字符串的内容值不会影响函数外部的实际参数的值,不过在函数内部修改列表的内容或值时,会连带影响函数外部的列表的内容或值。

def passFun(name, score):
    name = 'Macheal'
    print('函数内部修改过的名字和分数')
    print('======================')
    print('名字:', name)
    #添加一个分数,会同步修改函数的列表值
    score.append(85)
    print('分数:', score)

name1 = 'Andy'  #未调用函数前的名字设置值
score1 = [56, 84, 63] #未调用函数前的分数列表
print('函数调用前默认的名字和分数')
print('名字:', name1)
print('分数:', score1)
passFun(name1, score1)

print('函数内部被修改过并返回的名字和分数')
print('我们可以注意到名字没变,但分数被修改了')
print('名字:', name1)
print('分数:', score1)

变量的作用域

变量按其作用域分为全局变量与局部变量。

  • 全局(Global)变量:全局变量是声明在程序区块与函数之外且在声明语句以下的所有函数和程序区块都可以使用的变量。事实上,全局变量的使用应该相当谨慎,以免某个函数不小心赋了错误的值,进而影响整个程序的逻辑,其作用域适用于整个文件(*.py)。
  • 局部(Local)变量:适用于所声明的函数或流程控制范围内的程序区块,离开此范围,该变量的生命周期就结束了,超出其作用域而失效。

如何判断变量的适用范围或作用域呢?以第一次声明时所在的程序区块来表示其适用范围。下面举例说明全局变量和局部变量的不同。

score = [78, 65, 84, 91] # score为全局变量
for item in score:
    total = 0             #局部变量,存储累加的结果
    total += item         #每次total的值都从0开始,无法累加
print(total)

score是存储列表元素的全局变量,任何位置都可以调用它。total声明于for/in循环,离开循环体其生命周期就结束了。执行print(total)语句时,total变量已离开循环体,所以无法输出累加的结果。

所以上述程序必须修正如下,只有变量total为全局变量时,才能存储累加的结果。

score = [78, 65, 84, 91] # score为全局变量
total = 0                 # 全局变量,存储累加的结果
for item in score:
    total += item         #存储累加的结果值
print(total)

如果程序中有相同名称的全局变量与局部变量,就优先使用局部变量。

下面的程序用来说明在函数内必须优先使用局部变量,当离开函数,在函数体外时,则会采用全局变量。

def global_local():      #定义函数
    num=100
    print('num=',num)

num=500
global_local()           #输出局部变量100
print('num=',num)        #输出全局变量500

但是,如果要在函数内使用全局变量,就必须在函数中用global来声明该变量。

def global_local():          #定义函数
    global num
    print('num=',num)        #输出全局变量500
    num=100                  #全局变量值改成100

num=500
global_local()               #在函数中输出全局变量500
print('num=',num)            #输出全局变量的新设置值100

递归函数

递归(Recursion)是一种很特殊的算法,简单来说,对程序设计人员而言,“函数”(或称为子程序)不只是能够被其他函数所调用(或引用)的程序单元,在某些程序设计语言中,还提供了函数自己调用自己的功能,这种调用方式就是所谓的“递归”。递归在早期人工智能所用的程序设计语言(如Lisp、Prolog)中,几乎就是整个语言运行的核心,当然在Python中也提供了这项功能,因为递归汇集的时间可以延迟到执行时才动态决定。

何时才是使用递归的最好时机,是不是递归只能解决少数问题?事实上,任何可以用选择结构和重复结构来编写的程序代码,都可以使用递归来表示和编写。

递归的定义

谈到递归的定义,我们可以这样来形容,假如一个函数或子程序是由自身所定义或调用的,就称为递归,它至少需要具备以下两个条件:

  • 一个可以反复执行的递归过程。
  • 一个跳出执行过程的出口。

例如,数学上的阶乘问题就非常适合采用递归来运算,在数学中,我们一般用符号“!”来表示阶乘。假如4的阶乘写成4!,n的阶乘则可以写成:

n!=n*(n-1)*(n-2)**1

这个递归函数的算法用Python语言可以编写如下:

def factorial(i):
    if i==0:
        return 1
    else:
        ans=i * factorial(i-1)  #反复执行的递归过程
    return ans

其实递归可以被while或for取代,下例就是使用for循环来设计一个计算0!~n!的程序。

# 以for循环计算 n!
sum = 1
n=int(input('请输入n='))
for i in range(0,n+1):
    for j in range(i,0,-1):
        sum *= j    # sum=sum*j
    print('%d!=%3d' %(i,sum))
    sum=1

此外,递归因为调用对象的不同,可以分为以下两种。

  • 直接递归(Direct Recursion) 是指在递归函数中,允许直接调用该函数本身。例如: ```python def Fun(…): . . if … : Fun(…) . . }

- 间接递归(Indirect Recursion):是指在递归函数中,先调用其他递归函数,再从其他递归函数调用回原来的递归函数。
```python
def Fun1(...):       def Fun2(...):
    .                      .
    .                      .
    if ... :              if ... :
      Fun2(...)               Fun1(...)
    .                      .
    .                      .

“尾部递归”(Tail Recursion)就是程序的最后一条语句为递归调用,因为每次调用后,再回到前一次调用要执行的第一条语句就是return,所以后续不需要再执行任何语句了。

lambda表达式

lambda表达式又称为lambda函数,它没有函数名称。lambda函数只有一行程序语句,它的语法如下:

lambda 参数列表, ... : 表达式

其中,表达式之前的冒号“:”不能省略,表达式不能使用return指令。

自定义函数与lambda()有何不同?下面以一个简单的例子来进行说明。

def result(x, y): #自定义函数
    return x+y
    
result = lambda x, y : x + y  #lambda函数

注意,前面两行语句是自定义函数,函数名为result。最后一句是定义lambda函数。定义常规函数时,函数体一般有多行语句,但是lambda函数只能有一行表达式。另外,自定义函数都有函数名,lambda函数无名称,必须指定一个变量来存储运算的结果,再用变量名result来调用lambda函数,按其定义传入参数。在自定义的result()函数中,以return指令返回计算的结果,而lambda计算的结果由变量result存储。

下面的程序语句就是先定义lambda函数,再通过指定的变量result来调用它。

result = lambda x, y: x+y #表示lambda有两个参数
result(4, 7) #传入两个数值让lambda函数进行运算

Search

    微信好友

    博士的沙漏

    Table of Contents