python基础篇
经过前四章的铺垫,我们可以更进一步,学习更深层次的知识。本篇博客我要介绍的是有关函数方面的知识,让我们一起学习起来吧!!!
第 五 章 讲解函数
前言
函数是什么?函数是可重用的程序代码块。函数的作用,不仅可以实现代码的复用,更能实现代码的一致性。也就是说你修改代码只需要改动一处。
在写函数代码时,函数体中的代码和前面我们学过的知识的写法基本一致,只是对函数实现了封装,增加了函数的调用、传参、返回值等功能。
函数是什么?
- 一个程序由一个个任务组成,而函数就是代表一个任务或者一个功能
- 函数就是代码复用的通用机制
python函数种类有哪些?
python中函数有以下几种:
- 内置函数
如str()、list()、len()这些安徽省农户就是内置函数,可以直接使用 - 标准库函数
标准库函数我们需要通过import进行导包方能使用其中定义的函数 - 第三方库函数
第三方库需要下载安装,再用import导入使用 - 用户自定义函数
用户自己定义的函数以满足自己的需求,我们主要也是学习这个。
怎么定义和调用函数?
定义函数
语法格式:
def 函数名([参数列表]):
'''文档字符串'''
函数体/语句
各部分详解:
-
定义函数使用的是def,然后接一个空格和函数名称
python在执行def时,会创建一个函数对象,并绑定到函数名变量上 -
参数列表
圆括号内是形式参数列表,有多个参数则使用逗号隔开形式参数不需要声明类型,也不需要指定函数返回值类型
无参数,也必须保留空的圆括号
实参列表必须与形参列表一一对应
-
返回值问题(return)
如果函数体中包含 return语句,则结束函数执行并返回值如果函数体中不包含return 语句,则返回None 值
-
调用函数
要想调用函数,必须先定义函数内置函数对象会自动创建
标准库和第三方库函数,在import导入时会执行模块中的def语句创建函数对象
什么是形参?什么是实参?
【操作】定义一个函数,功能:实现两个数的比较返回其中的最大值
def printMax(a,b):
'''函数功能:实现两个数的比较返回其中较大值'''
if a>b:
print(a)
else:
print(b)
#函数调用
printMax(22,23)
在上面的案例中,定义函数时写的printMax(a,b),括号中的a和b就是“形式参数”,简称“形参”。也就是说形式参数是在定义函数时使用的。形式参数的命名只需要符合“标识符”的命名规则即可。
在调用函数时,传递的参数称为“实际参数”,简称“实参”,即上面代码中的22和23就是实参。
函数的注释
程序的可读性最重要,一般建议在函数体开始的部分附上函数定义说明,这就是“文档字符串”,也就是“函数的注释”。我们通过三个单引号或者三个双引号来实现,中间可以写入多行文字进行解释说明。
【操作】函数注释的使用
def print_zs(x):
'''根据传入的x 打印x个星号'''
print("*"*x)
#调用函数
print_zs(10)
我们可以调用help(函数名._ doc _)可以打印出函数的注释
help(print_zs)
Help on function print_zs in module __main__:
print_zs(x)
根据传入的x 打印x个星号
返回值
return返回值:
- 如果函数体中包含return语句,则结束安徽省农户执行并返回值
- 如果函数体中不包含return语句,则返回None值
- 如果想要返回多个值,使用列表、元组、字典、集合将多个值“存起来”
【操作】定义一个只打印无返回值的函数
def print_message():
print("xixishy-2")
#调用函数
print_message()
【操作】定义一个返回两个数的平均值的函数:
def my_avg(a,b):
return (a+ b)/2
#调用函数
x = my_avg(20,30)
print(x)
对函数深层次分析
在python中,一切皆对象。实际上,在执行def定义函数后,系统就创建了相应的函数对象。
def print_star(n):
print("*"*n)
#调用函数
print(print_star)
print(id(print_star))
x = print_star
x(3)
#运行结果
<function print_star at 0x000001C5BC8379D8>
1948782918104
***
运行def时,系统中会在创建函数对象,并通过函数名这个变量来引用
在执行c = print_star时,将函数名变量对应的值又赋给变量c:
因此c(3)和pirnt_star(3)执行效果一样,毕竟都是指向同一个函数对象。
注:python中圆括号表示调用函数,没有圆括号即将函数当成普通对象进行处理
变量的补充
变量的作用域 什么是全局变量和局部变量
变量起作用的范围叫做变量的作用域,不同作用域内的同名变量之间互不影响。
全局变量:
- 在函数和类的定义之外声明的变量 作用域为定义的模块 从定义的位置开始直到模块结束
- 全局变量降低了函数的通用性和可读性
- 全局变量一般做常量使用
- 函数内部要改变全局变量的值 使用global声明一下
局部变量:
- 在函数体中(包含形式参数)声明的变量
- 局部变量的引用比全局变量快 优先使用
- 如果局部变量和全局变量同名,则在函数内隐藏全局变量,只使用同名的局部变量
【操作】全局变量作用域示例:
a = 50
def f():
global a #如果要在函数内改变全局变量的值 增加global声明
print(a) #打印全局变量的值
a = 100
f()
print(a)
#执行结果:
50
100
【操作】全局变量和局部变量同名示例:
a = 50
def f():
a = 33 #同名的局部变量
print(a)
f()
print(a)
#执行结果:
33
50
【操作】输出局部变量和全局变量:
a = 100
def f(a,b,c):
print(a,b,c)
print(locals()) #打印输出局部变量
print('*'*20)
print(globals()) #打印输出全局变量
f(2,3,4)
#执行结果:
2 3 4
{'a': 2, 'b': 3, 'c': 4}
********************
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'E:\\PythonExec\\if_test01.py', 'a': 100, 'f1': <function f1 at 0x0000000002BB8620>}
局部变量和全局变量效率测试
局部变量的查询和访问速度比全局变量速度快,遇到问题应该优先考虑使用(尤其是涉及循环的时候)。
【操作】测试局部变量和全局变量效率:
import math
import time
def demo1():
start = time.time()
for i in range(10000000):
math.sqrt(50)
end = time.time()
print("耗时{0}".format((end-start)))
def demo2():
b = math.sqrt
start = time.time()
for i in range(10000000):
b(30)
end = time.time()
print("耗时{0}".format((end-start)))
demo1()
demo2()
#执行结果:
耗时2.115344524383545
耗时1.4064626693725586
参数的传递
函数中的参数传递本质上是由实参到形参的赋值操作。python“一切皆对象”,所有的赋值操作都是引用的赋值,参数的传递都是引用传递而不是值传递,具体分为两类:
- 对可变对象进行写操作,直接作用于原对象本身
- 对不可变对象进行写操作,会产生一个新的对象空间,并用新的值填写该空间(虽然像其他语言一样“值传递”但实质不是)
可变对象:字典、列表、集合、自定义对象
不可变对象:数字、字符串、元组、function等
传递可变对象的引用
传递参数是可变对象,实际上传递的还是对象的引用。在函数体中不创建新的对象拷贝而是可以直接修改所传递的对象。
【操作】参数传递(传递可变对象的引用):
b = [10,20]
def f(m):
print("m:",id(m)) #b和m是同一个对象
m.append(30) #由于 m是可变对象,不创建对象拷贝,直接修改这个对象
f(b)
print("b:",id(b))
print(b)
#执行结果:
m: 2173383456008
b: 2173383456008
[10, 20, 30]
传递不可变对象的引用
传递不可变对象,实际上传递的依然是对象的引用。但是赋值操作时由于不可变对象无法修改导致系统会新创建一个对象。
【操作】参数传递(不可变对象的引用):
a = 100
def f(m):
print('m',id(m)) #传递的是a对象的地址
m = m+200 #由于a是不可变对象 所以系统会新创建一个对象
print('m',id(m))
print(m)
f(a)
print('a',id(a))
#执行结果:
m 140727518428000
m 2173386956144
300
a 140727518428000
可以看出a和m一开始是同一个对象,但是经过赋值操作后m就变成一个新的对象
浅拷贝和深拷贝的认知
为了深入了解参数传递的底层原理,我们需要知道什么是浅拷贝和深拷贝。内置函数:copy()就是浅拷贝,deepcopy()就是深拷贝。
浅拷贝:不拷贝子对象的内容 只是拷贝子对象的引用
深拷贝:会连子对象的内容也拷贝一份,对子对象的修改不会影响原对象
【操作】浅拷贝和深拷贝:
import copy
def testCopy():
a = [10, 20, [5, 6]]
b = copy.copy(a)
print("a",a)
print("b",b)
b.append(30)
b[2].append(7)
print("这是浅拷贝")
print("a",a)
print("b",b)
def testDeepCopy():
a = [10, 20, [5, 6]]
b = copy.deepcopy(a)
print("a",a)
print("b",b)
b.append(30)
b[2].append(7)
print("这是深拷贝")
print("a",a)
print("b",b)
testCopy()
print('-------------')
testDeepCopy()
# 执行结果:
a [10, 20, [5, 6]]
b [10, 20, [5, 6]]
这是浅拷贝
a [10, 20, [5, 6, 7]]
b [10, 20, [5, 6, 7], 30]
-------------
a [10, 20, [5, 6]]
b [10, 20, [5, 6]]
这是深拷贝
a [10, 20, [5, 6]]
b [10, 20, [5, 6, 7], 30]
传递不可变对象包含的子对象是可变的情况:
'''
传 递 不 可 变 对 象 时 ,
不 可 变 对 象 里 面 包 含 的 子 对 象 是 可 变 的 。
则 方 法 内 修 改 了 这 个 可 变 对 象 ,
原 对 象 也 发 生 了 变 化
'''
a = (10,20,[5,6])
print("a:",id(a))
def testone(m):
print("m:",id(m))
m[2][0] = 66
print(m)
print("m:",id(m))
testone(a)
print(a)
# 执行结果:
a: 2173383349088
m: 2173383349088
(10, 20, [66, 6])
m: 2173383349088
(10, 20, [66, 6])
参数的类型
位置参数
函数调用时,实参默认是按照位置顺序传递,需要个数和形参匹配。
【操作】测试位置参数:
def f(a,b,c):
print(a,b,c)
f(1,2,3)
f(4,5) #报错 参数个数不匹配
默认值参数
我们可以为某些参数设置默认值,这样使得这些参数在传递时就是可选的,称为默认值参数,默认值参数应当放到位置参数后面。
【操作】测试默认值参数:
def f(a,b,c=10,d=20): #默认值参数必须放在位置参数后面
print(a,b,c,d)
f(8,9)
f(8,9,22,44)
#执行结果:
8 9 10 20
8 9 22 44
命名参数
我们可以按照形参的名称传递参数,称为命名参数也叫做关键字参数。
【操作】测试命名参数:
def f(a,b,c):
print(a,b,c)
f(8,9,19) #位置参数
f(c=10,a=20,b=30) #命名参数
#执行结果:
8 9 19
20 30 10
可变参数
可变参数指的是可变数量的参数,具体有两种:
- *param:一个星号,将多个参数收集到一个元组的对象中
- **param:两个星号,将多个参数收集到一个字典对象中
【操作】测试可变参数:
def testone(a,b,*c):
print(a,b,c)
testone(5,6,7,8)
def testwo(a,b,**c):
print(a,b,c)
testwo(8,9,name='shy-2',age=20)
def testhree(a,b,*c,**d):
print(a,b,c,d)
testhree(6,7,8,9,name='shy-2',age=20)
#执行结果:
5 6 (7, 8)
8 9 {'name': 'shy-2', 'age': 20}
6 7 (8, 9) {'name': 'shy-2', 'age': 20}
强制命名参数
在带星号的可变参数后面增加新的参数,必须在调用的时候强制命名参数。
【操作】强制命名参数:
def f(*a,b,c):
print(a,b,c)
f(2,3,4)
#会报错。由于 a是可变参数,将2,3,4 全部收集。造成 b和c没有赋值。
f(2,3,b=3,c=4)
lambda表达式和匿名函数
lambda表达式可以用来声明匿名函数。lambda函数是一种简单的、在同一行定义的函数的方法,实际上lambda函数生成了一个函数对象。
lambda表达式只允许包含一个表达式,不能包含复杂的语句,表达式计算的结果就是函数的返回值。
lambda表达式的基础语法:
lambda arg1,arg2,arg3…… : <表达式>
arg1,arg2,arg3是函数的参数,<表达式>相当于函数体,运算结果是表达式的运算结果
【操作】lambda表达式的使用:
f = lambda a,b,c:a+b+c
print(f)
print(f(2,3,4))
x = [lambda a:a*2,lambda b:b*3,lambda c:c*4]
print(x[0](6),x[1](7),x[2](8))
# 执行结果:
<function <lambda> at 0x000001FA07F63D08>
9
12 21 32
eval()函数
功能:将字符串str当成有效的表达式来求值并返回计算结果。
语法:eval(source[,globals[,locals]]) -> value
参数含义:
- source:一个python表达式或函数compile()返回的代码对象
- globals:可选,但必须是dictionary
- locals:可选,任意映射对象
【操作】测试eval函数:
s = "print('abcde')"
eval(s)
a = 10
b = 20
c = eval("a+b")
print(c)
dict = dict(a=100,b=200)
d = eval("a+b",dict)
print(d)
# 执行结果:
abcde
30
300
注意:eval()函数会将字符串当成语句来执行,因此存在安全隐患。要谨慎使用:字符串中包含删除文件的语句。
递归函数
递归函数指的是:自己调用自己的函数,在函数内部直接或间接的调用自己。每个递归函数必须包含两个部分:
- 终止条件:表示什么时候结束 一般用于返回值不再调用自己
- 递归步骤:把第n步的循环和第n-1步相关联
递归函数由于会创建大量的函数对象,过量的消耗内存和原酸能力,不要轻易使用
【操作】使用递归函数计算阶乘:
def factorial(n):
if n==1:return 1
return n*factorial(n-1)
for i in range(1,6):
print(i,'!=',factorial(i))
# 运算结果:
1 != 1
2 != 2
3 != 6
4 != 24
5 != 120
嵌套函数(内部函数)
嵌套函数:在函数内部定义的函数
【操作】嵌套函数的定义:
def f():
print("f is running")
def g():
print("g is running")
g()
f()
# 执行结果:
f is running
g is running
一般什么时候使用嵌套函数:
- 需要封装时——数据隐藏(外部无法访问嵌套函数)
- 嵌套函数可以让我们在函数内部避免使用重复代码
- 闭包
nonlocal关键字
- nonlocal:用来声明外层的局部变量
- global:用来声明全局变量
【操作】使用nonlocal声明外层局部变量:
a = 100
def outer():
b = 10
def inner():
nonlocal b #声明外部函数的局部变量
print("inner b:",b)
b = 20
global a #声明全局变量
a = 1000
inner()
print("outer b:",b)
outer()
print("a:",a)
# 运行结果:
inner b: 10
outer b: 20
a: 1000
LEGB规则
python查找名称时,是按照LEGB规则进行查找的:
LEGB -------------> Local-->Enclosed-->Global-->Built in
- Local:指的是函数或者类的方法内部
- Enclosed:指的是嵌套函数(一个函数包裹着另一个函数即闭包
- Global:值得是模块中的全局变量
- Built in :指的是python为自己保留的特殊名称
具体过程如下:
如果某个name映射在局部(local)命名空间中没有找到,接下来就会在闭包作用域 (enclosed)进行搜索,如果闭包作用域也没有找到,Python就会到全局(global)命名空 间中进行查找,最后会在内建(built-in)命名空间搜索 (如果一个名称在所有命名空间 中都没有找到,就会产生一个NameError)。
【操作】测试LEGB:
str = "global"
def outer():
str = "outer"
def inner():
str = "inner"
print(str)
inner()
outer()