1 函数的构建和调用
- 函数的介绍:函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
- 函数的作用:函数能提高应用的模块性,和代码的重复利用率。
- 函数的分类:内建函数、用户自定义函数
- 自定义函数:用def关键字一定一个函数
def 函数名(参数列表):
函数体
注意:
(1)函数名必须符合标识符的规范。
(2)默认情况下,参数值和参数名称是按函数声明中定义的顺序匹配起来的。
(3)函数内容以冒号开始,并且需要注意缩进。
(4)函数的第一行语句可以选择性地使用文档字符串,主要用于说明函数的功能。
(5)函数要么以函数体包含print()语句的形式输出内容,要么在函数结束时使用 return关键字+表达式 的形式返回值给函数,或者print()和return两者都用。
(6)return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。(注意return之后的代码不会被执行,所以return一般放在函数的最后)
(7)函数也是一个对象,用来保存一些可执行的代码,并且可以在需要时,可以对函数进行多次调用。
(8)函数的调用是通过函数名+括号()来执行的。
示例1:
def func1():
print('Python is easier to learn than java.')
# 调用函数func1
func1() # Python is easier to learn than java.
print(func1) # <function func1 at 0x00000000028F4D08>
示例2:
def func2(a,b):
sum=a*b
return sum # 返回sum的值
print(func2(1,2)) # 调用func2()函数,输出2
**注意:**func1 是函数对象,func1()是函数调用
2 参数传递
- 对象与变量
在Python中,类型属于对象,而变量是没有类型的,如以下代码
a=[1,2,3]
a='python'
[1,2,3]是列表(list)类型,'python’是字符串(string)类型,而变量a是没有类型,它仅仅是一个对象的引用(一个指针),可以指向列表类型对象,也可以指向字符串类型对象。
- 可变(mutable)与不可变(immutable)对象
在Python中,strings、tuples、numbers是不可更变对象,list、dict是可以修改的对象。
(1)不可变类型
a=66
print(a,id(a))
a=666
print(a,id(a))
执行结果:
66 1788375840
666 34320080
变量a赋值为66后重新赋值为666,实际是新生成一个int值对象666,然后再让a指向它,从变量a的id值变化就知道了。
(2)可变类型
b=[1,2,3]
print(b,id(b))
b[1]=10
print(b,id(b))
执行结果:
[1, 2, 3] 36020552
[1, 10, 3] 36020552
变量b赋值为[1,2,3],然后再令b[1]赋值为10,即列表b的第二个元素更改为10,而b本身的id值是没有改变的,只是其内部的一部分值被修改了。
- Python可变与不可变类型的参数传递
(1)不可变类型参数传递
类此c++的值传递,如 整型、字符串、元组。如fun(a),传递的只是a的值,没有影响a对象本身。比如在 fun(a)内部修改 a 的值,只是修改另一个复制的对象,不会影响 a 本身。
示例:
def func_int(a):
a=10
return a # 返回a的值
b=6
c=func_int(b)
print(c,b) # 输出结果为10和6
可以看到,在函数体内令参数a指向int对象10,在函数体外定义变量b指向int对象6,然后将变量b作为参数传递给 func_int() 函数,从输出结果可以看到,变量b的指向也没有因为函数的调用和参数的传递而发生改变,此时传递的只是变量b的值。
(2)可变类型参数传递
类似 c++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后fun外部的la也会受影响。进一步来说,可变对象在函数里修改了参数,那么在调用这个可变对象的函数里,原始的参数也会被改变。
示例:
def func_list(lst):
# 修改传入的列表
lst.append([1,2,3])
print('函数内取值:',lst)
return
lst=[11,12]
func_list(lst)
print('函数外取值:',lst)
输出结果为:
函数内取值: [11, 12, [1, 2, 3]]
函数外取值: [11, 12, [1, 2, 3]]
注意:如果传递的是一个可变对象,但又不想改变原来的参数,可以使用copy()函数来获取原来参数的值进行传递(列表可以用切片 list[:])
def func_list(lst):
# 修改传入的列表
lst.append([1,2,3])
print('函数内取值:',lst)
return
lst=[11,12]
func_list(lst.copy())
print('函数外取值:',lst)
输出结果为:
函数内取值: [11, 12, [1, 2, 3]]
函数外取值: [11, 12]
3 参数类型
3.1 形参和实参
- 形参:即形式参数,定义形参就相当于在函数内部声明了变量,但并不是赋值。
- 实参:即实际参数,函数指定了形参,那么在调用函数时就必须传递实参,实参将会赋值给对应的形参,简单来说就是,有几个形参就有几个实参。另外,实参可以传递任意类型的对象,而且函数在调用时,解析器不会检查函数的类型。
3.2 正式参数类型
调用函数时可使用的正式参数类型包含:必需参数、位置参数、关键字参数、默认参数、不定长参数
- 必需参数
必需参数是构建函数时声明的参数,调用函数时,必需参数的数量必须和声明时的一致。
示例:
def func3(a):
print(a)
# 调用func()函数,不加参数会报错
func() # TypeError: func() missing 1 required positional argument: 'a'
- 位置参数
位置参数就是将对应位置的实参赋值给对应位置的形参,位置参数受参数的顺序影响,参数的顺序(即位置)不同,得到的结果也不同。
示例:
def func4(parameter1,parameter2,parameter3):
print('第一个参数的值',parameter1)
print('第二个参数的值',parameter2)
print('第三个参数的值',parameter3)
print('\n')
a,b,c = 1,2,3
func4(a,b,c)
func4(c,a,b)
输出结果:
第一个参数的值 1
第二个参数的值 2
第三个参数的值 3
第一个参数的值 3
第二个参数的值 1
第三个参数的值 2
- 关键字参数
关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。
使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。
示例:
def func5(parameter1,parameter2):
print('第一个参数的值',parameter1)
print('第二个参数的值',parameter2)
print('\n')
func5(parameter1=1,parameter2='python')
func5(parameter2='python',parameter1=1)
输出结果:
第一个参数的值 1
第二个参数的值 python
第一个参数的值 1
第二个参数的值 python
注意:混合使用位置参数和关键字参数的时候必须将位置参数写到关键字参数前面去
- 默认参数
调用函数时,如果没有传递参数,则会使用默认参数。(默认关键字没有数量的限制)
示例:
def info(name,sex='男'):
# 默认性别为男
print('姓名:',name)
print('性别:',sex)
print('\n')
info(name='张三') # 直接用默认参数的值
info(name='丽丽',sex='女') # 改变默认参数的值
输出结果:
姓名: 张三
性别: 男
姓名: 丽丽
性别: 女
- 不定长参数
有时我们需要一个函数能处理比当初声明时更多的参数,这些参数叫做不定长参数。和其他参数不同的是,不定长参数声明时不用命名。
(1)加了星号 * 的参数会以元组(tuple)的形式导入,存放所有未命名的变量参数。
注意:
– 星号*的位置可以放在任何一个形参名称之前,但只能有一个不定长参数,且该不定长参数之后只能以关键字参数进行传递。
– 如果在函数调用时没有指定不定长参数,它就是一个空元组。我们也可以不向函数传递未命名的变量。
– 只带一个星号 * 的参数不能以关键字进行传递,只能以位置参数进行传递。
基本语法如下:
def functionname([formal_args,]*val_args_tuple):
'''
函数_文档字符串
'''
function_suite
return [expression]
示例1:
def func6(arg1,*vartuple):
'''打印任何传入的参数'''
print(arg1,vartuple)
func6(1,2,3)
func6(4)
输出结果:
1 (2, 3)
4 ()
示例2:
def func6_1(arg1,*vartuple,arg2):
'''打印任何传入的参数'''
print(arg1,vartuple,arg2)
func6_1(1,2,3,arg2=4) # 输出结果为 1 (2, 3) 4
示例3:
def sum_num(*num):
''' 定义一个函数,该函数可以相加任意个数的参数'''
sum=0
for i in num:
sum+=i
print(sum)
sum_num(1,2,3,4)
(2)加了两个星号 ** 的参数会以字典的形式导入,其传递类型只能以关键字参数的类型进行传递,字典的key就是关键字参数的名称,字典的value就是关键字参数的值。注意事项与加一个星号类似,这里不在赘述。
基本语法:
def functionname([formal_args,]**val_args_dict):
'''
函数_文档字符串
'''
function_suite
return [expression]
示例:
def func7(arg1,**vardict):
print(arg1, vardict)
func7(1,a=2,b=3)
值得注意的是,当加了一个星号的参数和加了两个星号的参数同时存在时,起作用只有加了两个星号的参数:
示例:
def func7(arg1,*valtuple,**vardict):
'''打印任何传入的参数'''
print(arg1, vardict)
func7(1,4,5,a=2,b=3) # 输出结果仍为 1 {'a': 2, 'b': 3}
(3)声明函数时,参数中星号 * 可以单独出现,并且单独出现星号 * 后的参数必须用关键字传入。而星号 * 的位置没有限制,但只能有一个
示例:
def func8(a,b,*,c,d):
return a+b+c+d
func8(1,2,3,4) # 会报错:func8() takes 2 positional arguments but 4 were given
print(func8(1,2,c=3,d=4)) # 输出结果为 10
3.3 参数解包
- 传递实参时,也可以在序列类型的参数前添加星号,这样它会自动的将序列中元素依次作为参数传递,但要求序列中的元素的个数必须和形参的个数一致。
示例:
def func8(a,b,c):
print(a,b,c)
tup=(1,2,3)
func8(*tup) # 输出结果为:1 2 3
4 匿名函数
- python 使用 lambda 来创建匿名函数。
- 所谓匿名,指的是不再使用 def 语句这样标准的形式定义一个函数。
- 匿名函数的特征:
(1)lambda 只是一个表达式,函数体比 def 简单很多。
(2)lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
(3)lambda 函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
(4)虽然lambda函数看起来只能写一行,却不等同于C或C++的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。 - 语法:lambda 函数的语法只包含一个语句
lambda [arg1 [,arg2,…argn]]:expression
示例:
sum=lambda a,b:a*b+b
print(sum(1,3)) # 输出结果为 6
使用help()函数可以查看函数的用法,使用方式:help(函数名称)
扩展
5 作用域
- 官方文档的一段话:A scope is a textual region of a Python program where a namespace is directly accessible. “Directly accessible” here means that an unqualified reference to a name attempts to find the name in the namespace.
- 作用域就是一个 Python 程序可以直接访问命名空间的正文区域。
- 在一个 python 程序中,直接访问一个变量,会从内到外依次访问所有的作用域直到找到,否则会报未定义的错误。
- Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。
- 变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。Python的作用域一共有4种,分别是:
(1)L(Local):最内层,包含局部变量,比如一个函数/方法内部。
(2)E(Enclosing):包含了非局部(non-local)的变量。比如两个嵌套函数,一个函数(或类) A 里面又包含了一个函数 B ,那么对于 B 中的名称来说 A 中的作用域就为 non-local。
(3)G(Global):当前脚本的最外层,比如当前模块的全局变量。
(4)B(Built-in): 包含了内建的变量/关键字等。,最后被搜索 - 规则顺序: L –> E –> G –>gt; B。
在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内置中找。 - global 和 nonlocal关键字
当内部作用域想修改外部作用域的变量时,就要用到global和nonlocal关键字了。
g_count = 0 # 全局作用域
def outer():
o_count = 1 # 闭包函数外的函数中
def inner():
i_count = 2 # 局部作用域
6 命名空间
- 官方文档的一段话:A namespace is a mapping from names to objects.Most namespaces are currently implemented as Python dictionaries。
- 命名空间(Namespace)是从名称到对象的映射,大部分的命名空间都是通过 Python 字典来实现的。
- 命名空间提供了在项目中避免名字冲突的一种方法。各个命名空间是独立的,没有任何关系的,所以一个命名空间中不能有重名,但不同的命名空间是可以重名而没有任何影响。
例子:一个文件夹(目录)中可以包含多个文件夹,每个文件夹中不能有相同的文件名,但不同文件夹中的文件可以重名。 - 一般有三种命名空间:
(1)内置名称(built-in names), Python 语言内置的名称,比如函数名 abs、char 和异常名称 BaseException、Exception 等等。
(2)全局名称(global names),模块中定义的名称,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。
(3)局部名称(local names),函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量。(类中定义的也是) - 三种命名空间的关系是如下:
从而决定了命名空间的查找顺序:
假设我们要使用变量a,则 Python 的查找顺序为:局部的命名空间去 -> 全局命名空间 -> 内置命名空间。
如果找不到变量a,它将放弃查找并引发一个 NameError 异常:
NameError: name 'a' is not defined。
- 命名空间的生命周期
命名空间的生命周期取决于对象的作用域,如果对象执行完成,则该命名空间的生命周期就结束。
因此,我们无法从外部命名空间访问内部命名空间的对象。 - locals()可以用来获取当前作用域的命名空间,返回值是一个字典(注意,locals()函数没有参数)
- 如果在全局作用域中调用locals()则获取全局命名空间,如果在函数作用域中调用locals()则获取函数命名空间。
- globals()可以在任意位置获取全局的命名空间。
# var1 是全局名称
var1 = 5
def some_func():
# var2 是局部名称
var2 = 6
def some_inner_func():
# var3 是内嵌的局部名称
var3 = 7
scope=locals() # scope是一个字典
print(scope)
print(scope['var1']) # 输出结果为5
7 递归函数
- 递归是解决问题的一种方式,它的整体思想,是将一个大问题分解为一个个的小问题,直到问题无法分解时,再去解决问题。
- 递归式函数有2个条件:
(1) 基线条件:问题可以被分解为最小问题,当满足基线条件时,递归就不执行了。
(2)递归条件:可以将问题继续分解的条件。 - 递归函数就是在函数中调用自己
示例:
# 用递归的方式来实现任意数的阶乘
# 分析:10!=10*9! 9!=9*8! …… 1!=1*1
def fn2(n):
# 参数n为要求阶乘的数字
# 基线条件:判断n为1的时候就不再递归
if n == 1:
# 1的阶乘就是它本身
return 1
# 递归条件 n!=n*(n-1)!
return n* fn2(n-1)
print(fn2(10)) # 输出结果为 3628800