函数是可重用的程序代码块。函数的作用,不仅可以实现代码的复用,更能实现代码的 一致性。一致性指的是,只要修改函数的代码,则所有调用该函数的地方都能得到体现。
在编写函数时,函数体中的代码写法和前面讲述的基本一致,只是对代码实现了封装,并增加了函数调用、传递参数、返回计算结果等内容。
Python中函数分为如下几类:
Python中,定义函数的语法如下:
def 函数名 ([参数列表]) :
‘’‘文档字符串’’’
函数体/若干语句
要点:
在定义时写的printMax(a,b)。a 和 b称为“形式参数”, 简称“形参”。形式参数是在定义函数时使用的。 形式参数的命名只要符合“标 识符”命名规则即可。
在调用函数时,传递的参数称为“实际参数”,简称“实参”。上面代码中, printMax(10,20),10 和20 就是实际参数。
程序的可读性最重要,一般建议在函数体开始的部分附上函数定义说明,这就是“文档字符 串”,也有人成为“函数的注释”。通过三个单引号或者三个双引号来实现,中间可以加入多行文字进行说明。
调用 help(函数名.doc)可以打印输出函数的文档字符串。
#测试形参、实参的基本用法
def printMax(a,b):
'''用于比较两个数的大小,打印较大的值'''
if a>b:
print(a,"较大值")
else:
print(b,"较大值")
printMax(10,20)
printMax(200,300)
help(printMax.__doc__)
help(printMax)
#结果
20 较大值
300 较大值
No Python documentation found for '用于比较两个数的大小,打印较大的值'.
Use help() to get the interactive help utility.
Use help(str) for help on the str class.
Help on function printMax in module __main__:
return返回值要点:
Python中,“一切都是对象”。实际上,执行def 定义函数后,系统就创建了相应的函数 对象。
def print_star(n):
print("*"*n)
print(print_star)
print(id(print_star))
c = print_star
c(3)
执行结果:
<function print_star at 0x0000000002BB8620>
45844000
***
上面代码执行 def 时,系统中会创建函数对象,并通过print_star 这个变量进行引用:
执行“c=print_star”后,显然将 print_star 变量的值赋给了变量c,内存图变成了:
可以看出变量c和print_star 都是指向了同一个函数对象。因此,执行c(3)和执 行 print_star(3)的效果是完全一致的。 Python 中,圆括号意味着调用函数。在没有圆括 号的情况下,Python会把函数当做普通对象。
变量起作用的范围称为变量的作用域,不同作用域内同名变量之间互不影响。变量分为:全 局变量、局部变量。
全局变量:
【操作】全局变量的作用域测试
a = 100 #全局变量
def f1():
global a #如果要在函数内改变全局变量的值,增加 global关键字声明
print(a) #打印全局变量a 的值 a = 300
f1()
print(a)
#执行结果: 100 300
【操作】全局变量和局部变量同名测试
a=100
def f1():
a = 3 #同名的局部变量 print(a)
f1()
print(a) #a仍然是100,没有变化
#执行结果: 3 100
【操作】 输出局部变量和全局变量
a = 100
def f1(a,b,c):
print(a,b,c)
print(locals()) #打印输出的局部变量
print("#"*20)
print(globals()) #打印输出的全局变量
f1(2,3,4)
#执行结果:
2 3 4
{
'c': 4, 'b': 3, 'a': 2}
####################
{
'__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 test01():
start = time.time()
for i in range(10000000):
math.sqrt(30)
end = time.time()
print("耗时{0}".format((end-start)))
def test02():
b = math.sqrt start = time.time()
for i in range(10000000):
b(30)
end = time.time()
print("耗时{0}".format((end-start)))
test01()
test02()
函数的参数传递本质上就是:从实参到形参的赋值操作。 Python中“一切皆对象”, 所有的赋值操作都是“引用的赋值”。所以,Python中参数的传递都是“引用传递”,不 是“值传递”。具体操作时分为两类:
传递参数是可变对象(例如:列表、字典、自定义的其他可变对象等),实际传递的还是对 象的引用。在函数体中不创建新的对象拷贝,而是可以直接修改所传递的对象。
b = [10,20]
def f2(m):
print("m:",id(m)) #b和m是同一个对象
m.append(30) #由于 m是可变对象,不创建对象拷贝,直接修改这个对象
f2(b)
print("b:",id(b))
print(b)
#执行结果: m: 45765960 b: 45765960 [10, 20, 30]
传递参数是不可变对象(例如:int、float、字符串、元组、布尔值),实际传递的还是对 象的引用。在”赋值操作”时,由于不可变对象无法修改,系统会新创建一个对象。
a = 100
def f1(n):
print("n:",id(n)) #传递进来的是a 对象的地址
n = n+200 #由于 a是不可变对象,因此创建新的对象n
print("n:",id(n)) #n已经变成了新的对象
print(n)
f1(a)
print("a:",id(a))
#执行结果: n: 1663816464 n: 46608592 300 a: 1663816464
n和 a一开始是同一个对象。给 n赋值后,n是新的对象。
内置函数: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 test01(m):
print("m:",id(m))
m[2][0] = 888
print(m)
print("m:",id(m))
test01(a)
print(a)
#运行结果:
a: 41611632
m: 41611632
(10, 20, [888, 6])
m: 41611632
(10, 20, [888, 6])
函数调用时,实参默认按位置顺序传递,需要个数和形参匹配。按位置传递的参数,称为: “位置参数”。
可以为某些参数设置默认值,这样这些参数在传递时就是可选的。称为“默认值参数”。 默认值参数放到位置参数后面。
def f1(a,b,c=10,d=20): #默认值参数必须位于普通位置参数后面
print(a,b,c,d)
f1(8,9)
f1(8,9,19)
f1(8,9,19,29)
可以按照形参的名称传递参数,称为“命名参数”,也称“关键字参数”。
def f1(a,b,c):
print(a,b,c)
f1(8,9,19) #位置参数
f1(c=10,a=20,b=30) #命名参数
可变参数指的是“可变数量的参数”。分两种情况:
def f1(a,b,*c):
print(a,b,c)
f1(8,9,19,20)
def f2(a,b,**c):
print(a,b,c
f2(8,9,name='wps',age=18)
def f3(a,b,*c,**d):
print(a,b,c,d)
f3(8,9,20,30,name='wps',age=18)
在带星号的“可变参数”后面增加新的参数,必须在调用的时候“强制命名参数”。
def f1(*a,b,c):
print(a,b,c)
#f1(2,3,4) #会报错。由于 a是可变参数,将2,3,4 全部收集。造成 b和c没有赋值。
f1(2,b=3,c=4)
lambda表达式可以用来声明匿名函数。lambda 函数是一种简单的、在同一行中定义函数 的方法。lambda函数实际生成了一个函数对象。
lambda表达式只允许包含一个表达式,不能包含复杂语句,该表达式的计算结果就是函数 的返回值。
lambda表达式的基本语法如下: lambda arg1,arg2,arg3… : <表达式> arg1/arg2/arg3为函数的参数。<表达式>相当于函数体。运算结果是:表达式的运算结果。
f = lambda a,b,c:a+b+c
print(f)
print(f(2,3,4))
g = [lambda a:a*2,lambda b:b*3,lambda c:c*4]
print(g[0](6),g[1](7),g[2](8))
功能:将字符串str当成有效的表达式来求值并返回计算结果。
语法: eval(source[, globals[, locals]]) -> value
参数:
source:一个 Python表达式或函数 compile()返回的代码对象
globals:可选。必须是dictionary
locals:可选。任意映射对象
# 测 试 e v a l ( ) 函 数
s = "print('abcde')"
eval(s)
a = 10
b = 20
c = eval("a+b")
print(c)
dict1 = dict(a=100,b=200)
d = eval("a+b",dict1)
print(d)
#执行结果
abcde
30
300
eval 函数会将字符串当做语句来执行,因此会被注入安全隐患。比如:字符串中含有删除文 件的语句。
递归函数指的是:自己调用自己的函数,在函数体内部直接或间接的自己调用自己。递归类 似于大家中学数学学习过的“数学归纳法”。 每个递归函数必须包含两个部分:
嵌套函数: 在函数内部定义的函数!
**一般在什么情况下使用嵌套函数? **
nonlocal 用来声明外层的局部变量。
global 用来声明全局变量。
# 测 试 n o n l o c a l 、 g l o b a l 关 键 字 的 用 法
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)
Python在查找“名称”时,是按照LEGB规则查找的:
Local–>Enclosed–>Global–>Built in
Local 指的就是函数或者类的方法内部
Enclosed 指的是嵌套函数(一个函数包裹另一个函数,闭包)
Global 指的是模块中的全局变量
Built in 指的是Python为自己保留的特殊名称。
如果某个name映射在局部(local)命名空间中没有找到,接下来就会在闭包作用域 (enclosed)进行搜索,如果闭包作用域也没有找到,Python就会到全局(global)命名空 间中进行查找,最后会在内建(built-in)命名空间搜索 (如果一个名称在所有命名空间 中都没有找到,就会产生一个NameError)。