python语言第七天笔记

1399-温同学

发表文章数:72

热门标签

,
首页 » Python » 正文

函数

1 函数的分类

(1)内置函数

  如前面使用的 str()、list()、len()等。

(2)标准库函数

  我们可以通过 import 语句导入库,然后使用其中定义的函数,如之前使用的海龟图的相关函数。

(3)第三方库函数

  Python 社区也提供了很多高质量的库。下载安装这些库后,也是通过 import 语句导入,然后可以使用这些第三方库的函数

(4)用户自定义函数

  用户自己定义的函数,显然也是开发中适应用户自身需求定义的函数。
  今天我们学习的就是如何自定义函数。

2 函数的定义与调用

  函数的定义:

def 函数名 ([参数列表]) :
	'''文档字符串'''
	函数体/若干语句

  要点:
  1.我们使用 def 来定义函数,然后就是一个空格和函数名称;
  (1) Python 执行 def 时,会创建一个函数对象,并绑定到函数名变量上。
  (2) Python 解释器仅仅执行 def 语句,不会执行相应的函数体,函数体在调用的时候才会执行,因此下面的程序段并不会报错:

def test01():
    test03()
def test02():
    print('02')
test02()

输出:

02

  上面的程序段中,在test01的函数中调用了test03,虽然没有定义test03,但也不会报错,因为Python 解释器仅仅执行 def 语句,不会执行相应的函数体。
2.参数列表
  (1) 圆括号内是形式参数列表,有多个参数则使用逗号隔开
  (2) 形式参数不需要声明类型,也不需要指定函数返回值类型
  (3) 无参数,也必须保留空的圆括号
  (4) 实参列表必须与形参列表一一对应
3.return 返回值
  (1) 如果函数体中包含 return 语句,则结束函数执行并返回值;
  (2) 如果函数体中不包含 return 语句,则返回 None 值。
  (3) 要返回多个返回值,使用列表、元组、字典、集合将多个值“存起来”即可。
3.调用函数之前,必须要先定义函数(被调用的函数,要么),即先调用 def 创建函数对象
  (1) 内置函数对象会自动创建
  (2) 标准库和第三方库函数,通过 import 导入模块时,会执行模块中的 def 语句,也就是在 import 的时候,就会执行相关的模块。
  例:定义一个函数,实现两个数的比较,并返回较大的值。

def printMax(a,b):
    '''实现两个数的比较,并返回较大的值'''
    if a>b:
        print(a,'较大值')
    else:
        print(b,'较大值')
printMax(10,20)
printMax(30,5)

输出

20 较大值
30 较大值

4.文档字符串
  文档字符串是对函数的解释说明,也可称为“函数注释”,在函数体的开头用三引号括起来,可以通过“函数名.doc”把文档字符串给调出来,也可以通过 help(函数名) 来查看。

def printMax(a,b):		# 形参是在函数定义时使用的,这里 a 和 b 就是形参
	'''实现两个数的比较,并返回较大的值'''
	if a>b:
		print(a,'较大值')
	else:
		print(b,'较大值')
printMax(10,20)			# 实参是在函数调用时使用的,这里10和20就是实参
printMax(30,5)
print(printMax.__doc__)	# printMax.__doc__返回的就是文档字符串
print('#'*30)
help(printMax)

输出

20 较大值
30 较大值
实现两个数的比较,并返回较大的值
##############################
Help on function printMax in module __main__:

printMax(a, b)
    实现两个数的比较,并返回较大的值

3 内存底层分析

  在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”后,c也成为了函数对象的引用,相应的内存图为
python语言第七天笔记
  我们可以看出变量 c 和 print_star 都是指向了同一个函数对象,因此,执行 c(3)和执行 print_star(3)的效果是完全一致的。
  类似的,我们也可以做如下操作:

zhengshu = int
zhengshu("234")

  这样 zhengshu 和 int 都是指向了同一个内置函数对象。当然,此处仅限于原理性讲解,实际开发中没必要这么做。

4 参数的传递

  本质:从实参到形参的赋值操作。
  在python中,一切赋值都是“引用赋值”,因此参数的传递都是“地址的传递”,而非“值传递”。
  如果实参是不可变的对象(字符串、元组、数值等),则形参也是这些对象的引用,它们在函数内部同样是不可变的;如果实参是可变对象(列表、字典、集合等),那么它们在函数内部依然可变。
  如果对形参进行赋值,那么函数内部的形参指向改变,则不会影响实参的指向。

b = [10, 20]
print("函数调用前,id(b):", id(b))
print(b)

def f2(m):
    print("####################进入函数###################3")
    print("m1:", id(m))     # b 和 m 是同一个对象
    m.append(30)            # 由于 m 是可变对象,函数内部可以对其进行修改
    print("m2:", id(m))
    m = [3, 4, 5]			 # 函数内部形参的指向改变了
    print('m3:', id(m))    
    m.append(50)			# 在末尾添加50
    print("####################离开函数###################3")


f2(b)
print("函数调用后,id(b):", id(b))
print(b)

输出

函数调用前,id(b)34545088
[10, 20]
####################进入函数###################3
m1: 34545088
m2: 34545088
m3: 40294400
####################离开函数###################3
函数调用后,id(b): 34545088
[10, 20, 30]

  可以看到,在函数内部,对形参的对象进行修改(在列表后面增加了一个30),会对实参造成影响,因为参数传递是地址传递;一旦形参指向发生变化,再对形参进行修改,将不会影响实参。

5 参数的几种类型

(1)位置参数

  函数调用时,实参默认按位置顺序传递,需要个数和形参匹配。按位置传递的参数,称为:“位置参数”。

def f1(a,b,c):
    print(a,b,c)
f1(2,3,4)
# f1(2,3)       # 报错,参数个数不匹配

输出

2 3 4

(2)默认值参数

  在函数定义的时候,可以预先为参数设定默认值,这些参数称为“默认值参数”。默认参数的规则与C++语言一致,定义时默认值参数放到位置参数后面,调用的时候默认参数位置上有实参,则使用实参,没有则使用默认参数。

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)

输出

8 9 10 20
8 9 19 20
8 9 19 29

(3)命名参数

  也可以按照形参的名称传递参数,称为“命名参数”,也称“关键字参数”。

def f1(a,b,c):
	print(a,b,c)
f1(8,9,19) #位置参数
f1(c=10,a=20,b=30) #命名参数

输出

8 9 19
20 30 10

(4)可变参数

可变参数指的是“可变数量的参数”。分两种情况:
  1.*param(一个星号),将多个参数收集到一个“元组”对象中。
  2.**param(两个星号),将多个参数收集到一个“字典”对象中。

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='gaoqi',age=18)		# 调用的时候需要手动输键值对 键=值
def f3(a,b,*c,**d):		# *越多,放在越后面
    print(a,b,c,d)
f3(8,9,20,30,name='gaoqi',age=18)	# 解释器能自动识别谁是元组,谁是字典

输出

8 9 (19, 20)
8 9 {'name': 'gaoqi', 'age': 18}
8 9 (20, 30) {'name': 'gaoqi', 'age': 18}

(5)强制命名参数

  如果带星号的“可变参数”并非放在最后面,那么在调用的时候必须强制命名参数,否则解释器无法识别。

def f1(*a,b,c):
	print(a,b,c)
f1(2,b=3,c=4)
#f1(2,3,4) #会报错。由于 a 是可变参数,将 2,3,4 全部收集。造成 b 和 c 没有赋值。

输出

(2,) 3 4

6 lambda 表达式和匿名函数

  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))		
# 用函数对象构成的列表,可以通过列表元素调用函数

输出

<function <lambda> at 0x0000000002BB8620>
9 1
2 21 32

7 eval()函数

  功能:将字符串中的内容当成有效的表达式来执行并返回结果。

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 函数会将字符串当做语句来执行, 因此会被注入安全隐患。 比如: 字符串中含有删除文件的语句。 因此, 使用时候, 要慎重!

8 递归函数

  递归函数指的是:自己调用自己的函数,在函数体内部直接或间接的自己调用自己,类似于数学中的书序归纳法。
  每个递归函数必须包含两个部分:
  1.终止条件
  表示递归什么时候结束,否则递归一直持续,直到挤爆栈空间,抛出异常。一般用于返回值,不再调用自己。
  2.递归步骤
  把第 n 步的值和第 n-1 步相关联。
例:编写求n的阶乘的函数,n作为参数传入

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

9 变量的作用域与栈帧

  变量起作用的范围称为变量的作用域,不同作用域内同名变量之间互不影响。

(1)全局变量与局部变量

  依据变量的作用域,可以把变量分为:全局变量、局部变量。
  全局变量:
  1.在函数和类定义之外声明的变量。作用域为定义的模块,从定义位置开始直到模块结束。
  2.全局变量降低了函数的通用性和可读性。在函数内部应尽量避免全局变量的使用。
  3.全局变量一般做常量使用。
  4.函数内要改变全局变量的值,使用 global 声明一下
  局部变量:
  1.在函数体中(包含形式参数)声明的变量。
  2.局部变量的引用比全局变量快,优先考虑使用。
  3.如果局部变量和全局变量同名,则在函数内隐藏全局变量,只使用同名的局部变量
  函数内部如果要修改全局变量的值,则需要先用 global 声明,如果仅仅是读,则不需要声明,直接用

a=100
b=200
def f1():
    print(a)        # 由于对a是读,所以可以不用声明
    global b        # 因为要对b进行写操作,所以需要用global声明
    b = 300
f1()
print(b)            # 全局变量b在函数中修改,修改对函数外部也有效

输出

100
300

  全局变量如果与局部变量同名,则使用局部变量

a=100
def f1():
	a = 3 #同名的局部变量
	print(a)
f1()
print(a) #a 仍然是 100,没有变化

输出

3
100

  如果全局变量与局部变量同名,则全局变量在函数内既不能读,也不能写,用 global声明也不行,都会报错。

a=100
def f1():
	a = 3 #同名的局部变量
	print(a)
    # global a      # 会报错
    # print(a)
f1()
print(a) #a 仍然是 100,没有变化

(2)栈帧(stack frame)

  函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中保存着函数所需要的各种信息,局部变量就保存在栈帧之中。
  当调用函数时,启动栈帧,调用结束,栈帧被删除。
  栈帧同样保存在内存的栈中,但它不是函数对象的引用。函数对象的引用是在定义函数的时候就有,而栈帧必须是在调用的时候才启动。
  假如函数中有局部变量a,调用的时候,它的值为4,那么可以用下面的示意图来解释栈帧。
python语言第七天笔记
  栈帧内部可以访问栈帧外部的对象,但栈帧外部却不能访问外部的对象,也就是说,函数内部可以访问全部变量,但函数外部却不能访问局部变量。

10 嵌套函数(内部函数)

(1)内部函数的概念

  所谓的内部函数,指的是函数内部的函数,比如在函数f1中定义了一个函数f2,那么f2只能在f1中被调用,不能在f1外面调用。
  嵌套函数可以避免函数内部的代码重复。
  例如,我要打印英文姓名和中文姓名,可以使用两个函数,也可以使用嵌套函数,就是把前面的两个函数中的重复部分合并,并封装成一个内部函数,在外部函数中加一个判断语句,根据输入的情况调用。

def printChineseName(name,familyName):
	print("{0} {1}".format(familyName,name))
def printEnglishName(name,familyName):
	print("{0} {1}".format(name, familyName))

可以将上面的两个函数合并成下面一个函数

def printName(isChinese,name,familyName):
	def inner_print(a,b):
		print("{0} {1}".format(a,b))
	if isChinese:
		inner_print(familyName,name)
	else:
		inner_print(name,familyName)
		
printName(True,"小七","高")
printName(False,"George","Bush")

(2)nonlocal关键字

  与函数内部使用全局变量的方法类似,但内部函数要使用外层函数的变量时,如果是读,则可以直接使用,如果是写,则需要加关键字nonlocal

def outer():
    a = 5
    b = 10
    def inner():
        nonlocal b              # 声明外部函数的局部变量
        print("inner b:",b)
        b = 20
        print('a:', a)          # 对外部函数的局部变量仅仅是写操作,所以不需要加关键字

    inner()
    print("outer b:",b)
outer()

输出

inner b: 10
a: 5
outer b: 20

  从输出来看,内部函数修改外部函数的局部变量,对外部函数也有效。

11 LEGB 规则

  Python 在查找“名称”时,是按照 LEGB 规则查找的:
  Local–>Enclosed–>Global–>Built in
  Local 指的就是函数或者类的方法内部
  Enclosed 指的是嵌套函数(一个函数包裹另一个函数,闭包)
  Global 指的是模块中的全局变量
  Built in 指的是 Python 为自己保留的特殊名称。
如果某个 name 映射在局部(local)命名空间中没有找到,接下来就会在闭包作用域
  (enclosed)进行搜索,如果闭包作用域也没有找到,Python 就会到全局(global)命名空间中进行查找,最后会在内建(built-in)命名空间搜索 (如果一个名称在所有命名空间中都没有找到,就会产生一个 NameError)。

def outer():
    def inner():
        print(str)
    inner()
outer()

输出

<class 'str'>

  内部函数在执行 print(str) 时,现在内部函数里找,看有没有 str 的标识符,如果没有,则在外部函数找,如果外部函数也没有,则看全局变量,如果全局变量也没有,则看有没有内置函数叫str。因为将其他对象转为字符串的函数叫str,这是一个内建函数,因此当没有人为定义str时,就会输出<class ‘str’>,并不会报错。

未经允许不得转载:作者:1399-温同学, 转载或复制请以 超链接形式 并注明出处 拜师资源博客
原文地址:《python语言第七天笔记》 发布于2020-11-14

分享到:
赞(0) 打赏

评论 抢沙发

评论前必须登录!

  注册



长按图片转发给朋友

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏

Vieu3.3主题
专业打造轻量级个人企业风格博客主题!专注于前端开发,全站响应式布局自适应模板。

登录

忘记密码 ?

您也可以使用第三方帐号快捷登录

Q Q 登 录
微 博 登 录