1 函数的定义

函数是一段具有特定功能的、可复用的语句组。python中函数用函数名来表示,并通过函数名进行功能调用。它是一种功能抽象,与黑盒类似,所以只要了解函数的输入输出方式即可,不用深究内部实现原理。函数的最突出优点是:

实现代码复用:减少重复性工作

保证代码一致:只需要修改该函数代码,则所有调用均能受影响

在python中可以把函数分为:系统自带函数、第三方库函数、自定义函数。需要重点掌握的是「自定义函数」。

自定义函数

自定义函数语法:

def 函数名([参数列表]):

函数体

return语句

# 示例

def add1(x):

x = x + 1

return x

函数通过「参数」和「返回值」来传递信息,并通过「参数列表」和「return语句」实现对两者的控制,详见下图:


注意事项:

函数定义时无需声明形参类型(由调用时的实参类型确定);也无需指定返回值类型(由return语句确定)

自定义函数即使没有任何参数,也必须保留一队空括号()

括号后面的冒号(:)必不可少

函数体相对于def关键字必须有缩进关系

python允许嵌套定义函数

return语句作用是结束函数调用,并将结果返回给调用者

return语句是可选的,可以出现在函数体任意位置

无return语句、有return语句没有执行、有return语句而没有返回值三种情况,函数都返回None

2 函数的调用

在定义好函数之后,有两种方式对其进行调用:

从本文件调用:直接使用函数名 + 传入参数,如add1(9)

从其他文件调用:这种方法有两种实现手段

先指定文件路径 + import 文件名,再用文件名.函数名(参数列表)调用

先指定文件路径 + from 文件名 import 函数名,再用文件名.函数名(参数列表)调用

# 从本文件调用

def add1(x):

x = x+2

return x

add1(10)

# 从其他文件调用:从名为addx的文件调用已经定义好的add1函数

import os

os.chdir('D:\\data\\python_file')

# 从其他文件调用方法1

import addx

addx.add1(4)

# 从其他文件调用方法2

from addx import add1

add1(9)

3 函数的参数

3.1 形参与实参

从上面可知,函数最重要的三部分就是参数、函数体、返回值,而参数分为形参和实参:

形参:定义函数时,函数名后面圆括号中的变量

实参:调用函数时,函数名后面圆括号中的变量

注意事项:

形参只在函数内部有效,一个函数可以没有形参,但必须有括号()

通常修改形参不影响实参;但如果传递给函数的是「可变序列」(列表、字典、集合),修改形参会影响实参

def printmax(a,b): # a, b是形参

if a>b:

print(a)

printmax(3,4) # 3, 4是实参

# 形参修改不影响实参

def add2(x):

x = x+2

return x

x = 10

print(add2(x))

print(x)

# 形参修改影响实参

def add2(x):

x.append(2)

return x

y = [1, 1] # 实参y是变序列(列表、字典、集合)

print(add2(y)) # 函数返回值

print(y) # 修改形参影响实参

3.2 参数的传递

在定义函数时无需指定形参类型,在调用函数时,python会根据实参类型来自动推断。而定义函数和调用函数的过程,可以简化为下面图中三步,实质就是通过参数和返回值传递信息,而参数的传递发生在第一和第三步。


函数参数多种多样,根据参数传递发生的先后顺序,可以从两个角度学习常见的一些参数:

定义函数(形参):默认值参数、可变参数

调用函数(实参):位置参数、关键字参数、命名关键字参数

同时,这些参数可以组合使用(可变参数无法和关键字参数组合),且参数定义的顺序从左至右分别是:位置参数 >> 默认值参数 >> 可变参数 / 关键字参数 / 命名关键字参数。参数传递还有一种高级用法——参数传递的序列解包。

3.2.1 默认值参数

默认参数就是在调用函数的时候使用一些包含默认值的参数。

# 默认值参数b=5, c=10

def demo(a, b=5, c=10):

print(a, b, c)

demo(1, 2)

注意事项:

默认值参数必须出现在参数列表最右端

调用带有默认值参数的函数时,可以对默认值参数进行赋值,也可以不赋值

默认值参数只能是不可变对象,使用可变序列作为参数默认值时,程序会有逻辑错误

可以使用 “函数名.defaults” 查看该函数所有默认参数的当前值

3.2.2 可变参数

可变参数就是允许在调用参数的时候传入多个(≥0个)参数,可变参数分为两种情况:

可变位置参数:定义参数时,在前面加一个*,表示这个参数是可变的,可以接受任意多个参数,这些参数构成一个「元组」,只能通过位置参数传递

可变关键字参数:定义参数时,在前面加**,表示这个参数可变,可以接受任意多个参数,这些参数构成一个「字典」,只能通过关键字参数传递

# 可变位置参数

def demo(*p):

print(p)

demo(1, 2, 3) # 参数在传入时被自动组装成一个元组

# 可变关键字参数

def demo(**p):

print(p)

demo(b='2', c='5', a='1') # 参数在传入时被自动组装成一个字典

3.2.3 位置参数

位置参数特点是调用函数时,要保证实参和形参的顺序一致、数量相同。

# 位置参数

def demo(a, b, c):

print(a, b, c)

demo(1, 2, 3)

3.2.4 关键字参数

关键字参数允许在调用时以字典形式传入0个或多个参数,且在传递参数时用等号(=)连接键和值。关键字参数最大优点,就是使实参顺序可以和形参顺序不一致,但不影响传递结果。

# 关键参数

def demo(a, b, c):

print(a, b, c)

demo(b=2, c=5, a=1) # 改变参数顺序对结果不影响

3.2.5 命名关键字参数

命名关键字参数是在关键字参数的基础上,限制传入的的关键字的变量名。和普通关键字参数不同,命名关键字参数需要一个用来区分的分隔符*,它后面的参数被认为是命名关键字参数。

# 这里星号分割符后面的city、job是命名关键字参数

def person_info(name, age, *, city, job):

print(name, age, city, job)

person_info("Alex", 17, city="Beijing", job="Engineer")

3.2.6 参数传递的序列解包

参数传递的序列解包,是通过在实参序列前加星号(*)将其解包,然后按顺序传递给多个形参。根据解包序列的不同,可以分为如下5种情况:

序列解包

示例

列表的序列解包

*[3,4,5]

元组的序列解包

*(3,4,5)

集合的序列解包

*{3,4,5}

字典的键的序列解包

若字典为dic={'a':1,'b':2,'c':3},则解包代码为:*dic

字典的值的序列解包

若字典为dic={'a':1,'b':2,'c':3},则解包代码为:*dic.values()

注意事项:

对实参序列进行序列解包后,得到的实参值就变成了位置参数,要和形参一一对应

当序列解包和位置参数同时使用时,序列解包相当于位置参数,且会优先处理

序列解包不能在关键字参数解包之后,否则报错

"""函数参数的序列解包"""

def demo(a, b, c):

print(a+b+c)

demo(*[3, 4, 5]) # 列表的序列解包

demo(*(3, 4, 5)) # 元组的序列解包

demo(*{3, 4, 5}) # 集合的序列解包

dic = {'a': 1, 'b': 2, 'c': 3}

demo(*dic) # 字典的键的序列解包

demo(*dic.values()) # 字典的值的序列解包

"""位置参数和序列解包同时使用"""

def demo(a, b, c):

print(a, b, c)

demo(*(1, 2, 3)) # 元组的序列解包

demo(1, *(2, 3)) # 位置参数和序列解包同时使用

demo(c=1, *(2, 3)) # 序列解包相当于位置参数,优先处理,正确用法

demo(*(3,), **{'c': 1, 'b': 2}) # 序列解包必须在关键字参数解包之前,正确用法

4 全局变量与局部变量

变量起作用的代码范围称为「变量的作用域」。不同作用域内变量名可以相同,但互不影响。从变量作用的范围分类,可以把变量分类为:

全局变量:指函数之外定义的变量,在程序执行全过程有效

局部变量:指在函数内部使用的变量,仅在函数内部有效,当函数退出时变量将不存在

需要特别指出的是,局部变量的引用比全局变量速度快,应考虑优先使用。

全局变量声明

有两种方式可以声明全局变量:

方式一:在函数外声明

方式二:在函数内部用global声明,又分为两种情况:

情况1:变量已在函数外定义,使用global声明。若进行了重新赋值,则赋值结果会覆盖原变量值

情况2:变量未在函数外定义,使用global在函数内部声明,它将增加为新的全局变量

特殊情况,若局部变量和全局变量同名,那么全局变量会在局部变量的作用域内被隐藏掉,即在自定义函数内生效的是局部变量。

d = 2 # 全局变量

def func(a, b):

c = a*b

return c

func(2, 3)

def func(a, b):

c = a*b

d = 2 # 局部变量

return c

func(2, 3)

"""声明的全局变量,已在函数外定义"""

n = 1

def func(a, b):

global n

n = b

c = a*b

return c

s = func("knock~", 2)

print(s, n)

"""声明的全局变量,未在函数外定义,则新增"""

def func(a, b):

c = a*b

global d # 声明d为全局变量

d = 2

return c

func(2, 3)

"""局部变量和全局变量同名,则全局变量在函数内会被隐藏"""

d = 10 # 全局变量d

def func(a, b):

d = 3 # 局部变量d

c = a+b+d

return c

func(1, 2)

d

5 lambda函数

lambda函数,又称匿名函数,即没有函数名字临时使用的小函数。其语法如下:

lambda 函数参数:函数表达式

注意:

- 匿名函数只能有一个表达式

- 该表达式的结果,就是函数的返回值

- 不允许包含其他复杂语句,但表达式中可以调用其他函数

lambda函数的使用场景,主要在两方面:

尤其适用于需要一个函数作为另一个函数参数的场合,比如排序

把匿名函数赋值给一个变量,再利用变量来调用该函数

def f(x, y, z):

return x+y+z # 位置参数

f(1, 2, 3)

def f1(x, y=10, z=10):

return x+y+z # 默认值参数

f(1)

"""把匿名函数赋值给一个变量,再利用变量来调用该函数,作用等价于自定义函数"""

f=lambda x,y,z:x+y+z

f(y=1,x=2,z=3) #关键值参数

L = ['ab', 'abcd', 'dfdfdg', 'a']

L.sort(key=lambda x: len(x)) # 按长度排序

L

L=[('小明',90,80),('小花',70,90),('小张',98,99)]

L.sort(key=lambda x:x[1],reverse=True) # 降序排序

L

6 递归

在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。但不是的函数调用自己都是递归,递归有其自身的特性:

必须有一个明确的递归结束条件,称为递归出口(基例),一般使用if……else结构控制递归的进入和退出

相邻两次重复之间有紧密的联系,前一次要为后一次做准备(通常前一次的输出就作为后一次的输入)

每一次递归,整体问题都要比原来减小,并且递归到一定层次时,要能直接给出结果


从上图可知,递归过程是函数调用自己,自己再调用自己,...,当某个条件得到满足(基例)的时候就不再调用,然后再一层一层地返回,直到该函数的第一次调用。递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出,在Python中,通常情况下,这个深度是1000层,超过将抛出异常。

案例:用递归实现阶乘


# 案例一:用递归实现阶乘

def fact(n):

if n==0: # 递归退出

return 1

else:

return n*fact(n-1) # 递归进入

fact(5)

# 案例二:实现字符串反转

# 方法1,先转成列表,调用列表的revers方法,再把列表转成字符串

s = 'abcde'

l = list(s)

l.reverse()

''.join(l)

# 方法2,切片的方法

s = 'abcde'

s[::-1]

# 方法3,递归的方法

s = 'abc'

def reverse1(s):

if s == '':

return s

else:

print(s)

return reverse1(s[1:])+s[0]

reverse1(s)