python函数讲座【修改】
python函数讲座
函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。函数能提高应用的模块性,和代码的重复利用率。简单地说,在Python中,函数是一组执行特定任务的相关语句。
Python 中函数的应用非常广泛,前面讲座中我们已经使用过过多个函数,比如 input() 、print()、range()函数等等,这些都是 Python 的内置函数,可以直接使用。
除了可以直接使用的内置函数外,Python 还支持自定义函数,即将一段有规律的、可重复使用的代码定义成函数,从而达到一次编写、多次调用的目的。
注意:和C语言、Visual Basic语言等函数定义不可以嵌套,调用可以嵌套不同,python函数定义和调用都可以嵌套,这一点和JavaScript函数一样。
Python函数的一般分类
(1)内置函数。Python语言内置了若干常用的函数,例如abs()、len()等等,在程序中可以直接使用。
(2)标准库函数。Python语言安装程序同时会安装若干标准库,例如math、random等等。通过import语句,可以导入标准库,然后使用其中定义的函数。
(3)第三方库函数。Python社区提供了许多其他高质量的库,如Python图像库等等,需要下载安装。下载安装这些库后,通过import语句,可以导入库,然后使用其中定义的函数。
(4)用户自定义函数。本讲座将讨论函数的定义和调用方法。
自定义函数的简单规则:
函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()和冒号:。
任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
函数内容(函数体)缩进。
Python 定义函数的语法格式
Python 定义函数使用 def 关键字,一般格式如下:
def 函数名([参数列表]):
["""docstring"""]
函数体
[return [返回值]]
各部分的含义如下:
其中[ ]表示其内的部分是可选的即不一定有——可写有可不写,[ ]本身不写。
关键字def标志这一个函数头部的开始。
函数名:其实就是一个符合 Python 语法的标识符,但不建议读者使用 a、b、c 这类简单的标识符作为函数名,函数名最好能够体现出该函数的功能。
参数列表:是可选的。设置该函数可以接收多少个参数,多个参数之间用逗号( , )分隔。即使函数不需要参数,也必须保留括号()。
一个冒号(:)是标志着函数头部的结束。
docstring:是documentation string的缩写。它用来简单地解释函数的作用。
函数体:由一个或多个有效python语句组成。语句必须有相同的缩进(通常是4个空格)。
[return [返回值] ]:整体作为函数的可选参参数,用于设置该函数的返回值。也就是说,一个函数,可以用返回值,也可以没有返回值,是否需要根据实际情况而定。函数体中没有 return 语句时,函数运行结束会隐含返回一个 None 作为返回值,类型是 NoneType,与 return 、return None 等效,都是返回 None。
函数中可以有多条return语句,只要执行了一条return语句,程序返回到调用程序。
注意,在创建函数时,即使函数不需要参数,也必须保留一对空的“()”,否则 Python 解释器将提示“invaild syntax”错误。
函数中使用Docstring(文档字符串)的例子
示例中,我们在函数标头下方有一个文档字符串。 我们通常使用三引号,以便文档字符串可以扩展到多行。 该字符串可以作为函数的__doc__属性使用,我们可以使用__doc__查看函数中使用Docstring(文档字符串),例如
def greet(name):
"""This function greets to the person passed in as parameter"""
print(greet.__doc__) # 查看函数中使用Docstring(文档字符串)
运行之,参见下图:

例、#定义一个比较字符串大小的函数
#函数的定义(声明)
def s_max(s1,s2):
if s1>s2:
return s1
else:
return s2
Python函数的调用
python函数的应用一般需要:先定义(声明)、后调用,函数调用函数例外,详见 https://blog.csdn.net/couragehope/article/details/83031932。
定义一个函数只给了函数一个名称,指定了函数里包含的参数,和代码块结构。
这个函数的基本结构完成以后,你可以通过另一个函数调用执行,也可以直接从Python提示符执行。要调用函数,只需键入带有适当参数的函数名。
调用函数一方,可以使用变量来接收函数的返回结果。
s1= s_max('li','wang') #函数的调用
print(s1)
参见下图:

函数参数
常见的普通参数也叫必须参数、位置参数,前面的例子就是。实参和形参在顺序上和数量上必须要保持一致,否则可能造成异常,具体而言:实参和形参数量不同时将抛出异常报错;,实参和形参在顺序上不一致时,若实参和形参类型不一致将抛出异常报错,若实参和形参类型一致,虽然不抛出异常报错,但会产生结果与预期不符。
关键字参数指调用函数时使用形参的名字来确定传入的参数值,即调用函数时使用 形参名=值 这样的形式,这种情形不需要在乎参数的位置顺序。
可以将上例中s1= s_max('li','wang') #函数的调用
改为:s1= s_max(s1='li', s2='wang') #函数的调用
参见下图:

这种方式,参数从左至右的关系已不再重要了,因为参数是通过变量名进行传递的,而不是通过其位置,例如:
def f(a,b,c):
print(a,b,c)
f(c=3,a=1,b=2) #函数的调用
参见下图:

函数的定义和调用示例2、计算并返回第n阶调和数(1 + 1/2 + 1/3 + … + 1/n)的函数,输出前n个调和数
def harmonic(n): #自定义函数:计算n阶调和数(1 + 1/2 + 1/3 + … + 1/n)
total = 0.0
for i in range(1, n+1):
total += 1.0 / i
return total
n = int(6) #调用前用定义的函数
for i in range(1, n+1): #输出前n个调和数的值
print(harmonic(i))

参数的作用
函数的参数,增加函数的通用性,针对相同的数据处理逻辑,能够适应更多的数据。
1.在函数内部,把参数当作变量使用,进行需要的数据处理。
2.函数调用时,按照函数定义的参数顺序,把希望在函数内部处理的数据,通过参数传递。
形式参数(简称形参)和实际参数(简称实参)
形参:定义函数时,小括号中的参数,是用来接收参数用的,在函数内部作为变量使用。
实参:调用函数时,小括号中的参数,是用来把数据传递到函数内部用的。
声明函数时声明的形式参数,等同于函数体中的局部变量,在函数体中的任何位置都可以使用。
局部变量和形式参数变量的区别在于,局部变量在函数体中绑定到某个对象;而形式参数变量则绑定到函数调用代码传递的对应实际参数对象。
Python 函数参数的传递方式
python的参数传递机制
按对象引用调用(call by object reference)。调用者和被调用者之间共享这个对象,而对于不可变对象,由于并不能真正被修改,因此,修改往往是通过生成一个新对象然后赋值来实现的。
【实参是使用 按值调用(call by value) 来传递的(其中的 值 始终是对象的引用 而不是对象的值) [1] 。[注1]:实际上,按对象引用调用(call by object reference) 这种说法更好,因为,传递的是可变对象时,调用者能发现被调者做出的任何更改(插入列表的元素)。节选自官方文档 https://docs.python.org/zh-cn/3/tutorial/controlflow.html#defining-functions】
需要注意可变(mutable)与不可变(immutable)的影响。
【可变(mutable)与不可变(immutable)的含义
在 python 中,strings, tuples, 和 numbers 是不可变(不可更改)的对象,而 list,dict 等则是可变的对象。
不可变对象就是那些一旦被创建,它们的状态就不能被改变的对象,每次对它们的改变都是产生了新的对象。
可变对象就是那些创建后,状态依然可以被改变的对象。
不可变类型:变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变a的值,相当于新生成了a。即一旦试图更改其值,将会构造一个新的对象而非对原来的值进行更改。
可变类型:变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将列表 la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。即其值可以被更改。】
可变(mutable) 对象参数与不可变(immutable)对象参数的区别
由于 Python 中的变量存放的是对象引用,所以对于不可变对象而言,尽管对象本身不可变,但变量的对象引用是可变的——变化的情况实际上是由于变量重新指向了新的对象,而不是修改了原来的对象。运用这样的机制,有时候会让人产生糊涂,似乎可变对象变化了。如下面的代码:
i = 73
i += 2
不可变的对象的特征没有变,依然是不可变对象,变的只是创建了新对象,改变了变量的对象引用。参见下图:
对于可变对象,其对象的内容是可以变化的。当对象的内容发生变化时,变量的对象引用是不会变化的。如下面的例子。
m=[5,9]
m+=[6]
参见下图:
【Mutable(可变)对象
可变对象可以在其 id() 保持固定的情况下改变其取值。
Immutable(不可变)对象
具有固定值的对象。不可变对象包括数字(numbers)、字符串(strings)和元组(tuples)。这样的对象不能被改变。如果必须存储一个不同的值,则必须创建新的对象。不可变对象不允许对自身内容进行修改。如果我们对一个不可变对象进行赋值,实际上是生成一个新对象,再让变量指向这个对象。哪怕这个对象简单到只是数字 0 和 1。】
下面程序演示了不可变参数传递的情况:
def add_fun(num):
num +=1
print(num, id(num))
return
num = 3
add_fun(num)
print(num, id(num))
【顺便提示:id()的值不是固定不变的——此值系统为对象分配的内存地址,在你练习时显示的不同值是正常的。】
这段代码运行结果是:
4 1857150478736
3 1857150478704
不可变的对象的特征没有变,依然是不可变对象,变的只是创建了新对象,改变了变量的对象引用。
下面程序演示了可变参数传递的情况:
def list_append_fun(list_t):
list_t.append("abc")
print(list_t, id(list_t))
return
list_t = [1,2,3]
print(list_t, id(list_t))
list_append_fun(list_t)
print(list_t, id(list_t))
这段代码运行结果是:
[1, 2, 3] 2005885237248
[1, 2, 3, 'abc'] 2005885237248
[1, 2, 3, 'abc'] 2005885237248
当传递列表或者字典时,如果改变引用的值,就改变了原始对象。对于可变对象参数,其对象的内容是可以变化的。当对象的内容发生变化时,变量的对象引用是不会变化的。
变量的作用域
局部变量、全局变量和类型成员变量
局部变量是在函数内部定义的变量,只能在函数内部使用,临时保存函数内部需要使用的数据,函数执行结束后,函数内部的局部变量,会被系统回收。
不同的函数,可以定义相同的名字的局部变量,但是各用各的不会产生影响。
如果在一个函数中定义的局部变量(或形式参数变量)与全局变量重名,则局部变量(或形式参数变量)优先,即函数中定义的变量是指局部变量(或形式参数变量),而不是全局变量。
全局变量:在一个源代码文件中,在函数和类定义之外声明的变量。
全局变量的作用域为其定义的模块,从定义的位置起,直到文件结束位置。
全局变量是在函数外部定义的变量,(没有定义在某一个函数内),所有函数内部都可以使用这个变量。
通过import语句导入模块,也可以通过全限定名称“模块名.变量名”访问。或者通过from…import语句导入模块中的变量并访问。
例、
#局部变量全局变量的例子
num = 100 #全局变量
def f():
num = 105 #局部变量
print("输出函数内的局部变量的值:",num)
#测试代码
f()
print("输出函数外的全局变量的值:",num)

在函数体中,可以引用全局变量,但如果函数内部的变量名是第一次出现且在赋值语句之前(变量赋值),则解释为定义局部变量(不存在),参见下例:
m = 100
n = 200
def f():
print(m+10) #引用全局变量m
n =n + 10 #注意这句错误
#测试代码
f()

变量的作用域是程序中识别变量的部分。函数内部定义的参数和变量从外部看不到。因此,它们具有局部作用域。一旦我们从函数返回,它们就会被销毁。因此,函数不记得以前调用的变量的值。
下面例子来说明函数中变量的作用域:
def my_func():
x = 10
print("Value inside function:",x)
x = 20
my_func()
print("Value outside function:",x)
运行之,输出参见下图:

下面的例子来说明函数中变量的作用域:
def my_func():
x = 10
print("Value inside function:",x)
x = 20
my_func()
print("Value outside function:",x)
运行之,输出参见下图:

这里,我们可以看到x的初始值是20。尽管函数my_func()将x的值更改为10,但它不会影响函数外部的值。这是因为函数内部的变量x与外部的变量x不同,虽然它们有相同的名称,但它们是两个不同的变量,具有不同的作用域。
类成员变量
类成员变量是在类中声明的变量,包括静态变量和实例变量,其有效范围(作用域)为类定义体内。
在外部,通过创建类的对象实例,然后通过“对象.实例变量”访问类的实例变量,或者通过“类.静态变量”访问类的静态变量。
这部分内容将在面向对象程序设计讲座介绍。
python函数参数的进一步介绍
现在介绍默认参数、可变参数(*开头的参数、**开头的参数)。
默认参数
默认参数就是定义函数时,形参给定一个缺省默认值。
下面给出一个求幂的例子
由于我们经常计算x2,所以,可以把第二个参数n的默认值设定为2,当我们调用power(5)时,相当于调用power(5, 2)。
def power(x, n=2):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
print("power(3,2)=",power(3,2))
print("power(3,2)=",power(3))
print("power(3,4)=",power(3,4))

可以改变默认参数的值,如果没有给默认形参一个值作为实参,那么就会调用默认形参的值作为实参,因此会得到默认的值;如果我们在调用函数时输入了一个非默认形参值,这时候默认形参的值会发生变化,变为输入的那个值!
*开头的参数——可变数量参数(不定长参数)
在Python函数中,还可以定义可变参数。顾名思义,可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。带有*的形参,可以容纳很多个参数(包括0个参数)。
def change(*str):
for i in str:
print(i)
return
change("王一民","中国山东","软件开发爱好者")

带有*的形参,本例中是*str,调用该函数时,可以传入任意个参数,包括0个参数。
**开头的参数
带有**的形参,允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict类型数据——会将多个值转成一个字典来处理。
例
def show_three(**kw):
print(type(kw))
print(kw)
show_three(name='张三',age='20',sex='男')
运行情况参见下图:

组合参数,就是使用多种参数,定义函数时参数时的顺序有要求:普通参数(必须参数)、默认值参数、*开头的参数、**开头的参数。下面给出一个例子:
#函数定义
def hs(a1,a2,a3=10,*a4,**cs):
print('a1=', a1, 'a2=',a2, 'a3=',a3, '*4a=',a4, '**cs=',cs)
xx={'name':'xiaozhi','age':'18','interesting':'basketball'}
hs(1,2,3,4,5,6,7,m=26,n=25,**xx) #第一次调用
tu=(1,2,3,4,5,6,7,8,'zhao')
zd={'m':26, 'n':25, 'x':'wang'}
hs(*tu, **zd) #第二次调用
运行情况参见下图:

在第一次调用函数时,1、2分别给a1,a2,形参a3=10,但是传入实参为3,改变了原来的值,因此a3=3,*a4 是不定长参数,因此4、5、6、7给*a4,以元组的形式输出,m、n以及**xx 的值给**cs,以字典形式输出。
在第二次调用函数时,tu为一个元组,用*tu调用时输出 a1=1,a2=2,a3=3,*4a=(4,5,6,7,8, 'zhao'),用**zd调用时以字典形式输出{'m': 26, 'n': 25, 'x': 'wang'}。
再给出一个例子:
def myFun(args, *args1, **args2):
print(args)
print(args1)
print(args2)
myFun('测试:','李明', 9, '小学生', name='刘美媚', job='软件测试', Gender='女')
运行情况参见下图:

匿名函数(Anonymous Function)
匿名函数格式:
lambda [参数列表]: 表达式
说明
Python语言使用lambda关键字来创建匿名函数。
参数列表不需要小括号,多个参数之间用英文逗号分隔。无参就不写参数。
冒号用来分割参数列表和表达式部分。
不需要使用return。表达式的值,就是匿名函数的返回值。表达式中不能出现等号。
lambda表达式(匿名函数)只能写在一行上。
匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数,如果有参数,将对应的参数放进括号。
所谓匿名,即不再使用def语句这样标准的形式定义一个函数,没有函数名字。
匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量。
匿名函数调用方式
1)直接赋值给一个变量,然后再像一般函数调用,如:
c = lambda x,y,z: x*y*z
print(c(2,3,4)) #输出 24
2)直接后面传递实参,如:
print((lambda x,y: x if x> y else y)(101,102)) #输出 102
下面给出例子并运行之。
f = lambda x: x * x
print(f(3))
运行之,参见下图:

也可以把匿名函数作为别的函数的返回值返回。例:
def build(x, y):
return lambda: x * x + y * y
f=build(1, 2) # 调用
print(f()) # 注意这句
运行之,参见下图:

递归函数(Recursive function)
递归算法(Recursive algorithm)在计算机科学中是指一种通过重复将问题分解为同类的子问题而解决问题的方法。递归算法是一种直接或者间接调用自身函数或者方法的算法。
运用递归的条件:每一步进行的操作基本相同,并且问题规模逐渐减小。
递归的过程
递归,顾名思义,其包含了两个意思:递 和 归,这正是递归思想的精华所在。递归就是有去(递去)有回(归来),用递归求4!如下图所示:

递归函数(Recursive function),一个函数在内部调用自己,那么这个函数是递归函数。递归会反复使用本身,每递归一次,越接近最终的值。当一个问题可以由许多相似的小问题解决, 可以考虑使用递归函数。随着递归的深入,问题规模相比上次都应所减小。return函数本身的方法保证了递归的持续进行,但是如果没有明确的结束条件,递归会无限进行下去。所以当已经到了问题解决的程度, 应该告诉函数结束递归,这就需要明确的结束条件。
下面是用递归求阶乘的python代码
def fun(n):
if n == 0 or n == 1:
return 1
else:
return (n * fun(n - 1))
num = int(input("请输入一个正整数: "))
a = fun(num)
print(a)
运行之,参见下图:

经典递归问题
汉诺塔:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。此问题等价于:
设a,b,c是3个柱子。开始时,在柱子a上有一叠共n个圆盘,这些圆盘自下而上,由大到小地叠在一起。各圆盘从小到大编号为1,2,…,n,现要求将柱子a上的圆盘移到柱子c上,在移动圆盘时可以借助b柱子,但应遵守以下移动规则:
规则1:每次只能移动1个圆盘;
规则2:任何时刻都不允许将较大的圆盘压在较小的圆盘之上;
规则3:在满足移动规则1和2的前提下,可将圆盘移至a,b,c中任一柱子上。

目标:把a柱子上的n个盘子移动到c柱子
递归的思想就是把这个目标分解成三个子目标
子目标1:将前n-1个盘子从a移动到b上
子目标2:将最底下的最后一个盘子从a移动到c上
子目标3:将b上的n-1个盘子移动到c上
然后每个子目标又是一次独立的汉诺塔游戏,也就可以继续分解目标直到N为1
用递归求汉诺塔的python代码
def hanoi(n, a, b, c):
if n == 1:
print(a, '-->', c)
else:
hanoi(n - 1, a, c, b)
hanoi(1 , a, b, c)
hanoi(n - 1, b, a, c)
# 调用
n = int(input("请输入汉诺塔的层数并回车:"))
hanoi(n, 'A', 'B', 'C')
运行之,参见下图:

嵌套函数(Nested function)和闭包(closure)
Python允许函数的嵌套,即在函数体中定义函数,内部的函数称为嵌套函数(nested function),也叫内部函数(inner function)。为了执行内部函数,必须调用外部函数。 如果不调用外部函数,内部函数将永远不会执行。
怎么在函数外部调用内部函数?不能直接调用内部函数,需要先调用外部函数,在调用内部函数,否则出错,请留意下面例子调用的写法。
例子1、
def f1(x):
print("from f1",x)
def f2(y):
print("from f2",y)
f2("xyz") # 调用内层函数f2
f1(123) #调用
运行之,参见下图:

请注意这种情况下函数的访问顺序,在上面的示例中,f2()是在 f1()内部定义的,因此它是一个内部函数。 要调用 f2() ,必须先调用 f1(), 然后,f1()将继续调用在其中定义的 f2()。
对上例,修改如下,请仔细对比:
def f1(x):
print("from f1",x)
def f2(y):
print("from f2",y)
return f2 #注意此处改动了
f1(123)("xyx") #注意此处改动了。本句和res=f1(123)和res("xyz")这两句,效果是一样
print("---------")
res=f1(123)
res("xyz")
运行之,参见下图:

注意修改处,这样一来,调用函数时,除使用先外后内方式调用,还可以采用了多括号方式调用。
一般而言,调用一个函数是加一个括号。
如果看见调用函数括号后还有一个括号,说明第一个函数(外部函数)返回了一个函数(内部函数)——第一个函数(外部函数)使用 return 内部函数名,前面的括号是外部函数的,后面的括号是内部函数的。
有了上述基础,理解闭包就容易了。
闭包(closure)
在一个外部函数内定义了一个内部函数,内部函数里运用了外部函数的临时变量,并且外部函数的返回值是内部函数的引用,这样就构成了一个闭包(closure)。【如果一个内部函数访问外部函数作用域的变量,并返回值是这个内部函数,这样就构成了一个闭包。】
以下是在 Python 中创建闭包所需满足的条件:
必须有一个嵌套函数。
内部函数必须引用在enclosing scope(外层/封闭作用域)中定义的值——内部函数必须引用外部函数中的变量。
enclosing function(外层/封闭函数)必须返回嵌套函数。
计算一个数的 n 次幂,用闭包可以写成下面的代码:
#闭包函数,其中 exponent 是外层变量
def nth_power(exponent):
def exponent_of(base):
return base ** exponent
return exponent_of # 返回值是 exponent_of 函数,注意不能带括号
square = nth_power(2) # 计算一个数的平方
cube = nth_power(3) # 计算一个数的立方
print(square(2)) # 计算 2 的平方
print(cube(2)) # 计算 2 的立方
运行之,参见下图:

在执行完 square = nth_power(2) 和 cube = nth_power(3) 后,外部函数 nth_power() 的参数 exponent 会和内部函数 exponent_of 一起赋值给 squre 和 cube,这样在之后调用 square(2) 或者 cube(2) 时,程序就能顺利地输出结果,而不会报错说参数 exponent 没有定义。
关于python之 Nested Function和closure延展阅读 https://blog.csdn.net/cnds123/article/details/129684125
__closure__ 属性
Python闭包比普通的函数相比,多了一个 __closure__ 属性,该属性记录着自由变量的地址。当闭包被调用时,系统就会根据该地址找到对应的自由变量,完成整体的函数调用。
以上面提到的nth_power() 为例,当其被调用时,可以通过 __closure__ 属性获取外层变量(对本例而言是程序中的 exponent 参数)存储的地址,例如:
def nth_power(exponent):
def exponent_of(base):
return base ** exponent
return exponent_of
square = nth_power(2)
#查看 __closure__ 的值
print(square.__closure__)

Python变量的认识理解 https://blog.csdn.net/cnds123/article/details/116768499
关于Python的全局变量、局部变量、类变量、实例变量,以及global和nonlocal等更多详情可见https://blog.csdn.net/cnds123/article/details/129488233
关于Python函数的几点说明 https://blog.csdn.net/cnds123/article/details/127924671
关于Python的函数的几点特别说明 https://blog.csdn.net/cnds123/article/details/115770008