人工智能入门与实战第一季:python基础语法
函数是实现某一功能的、可以复用的代码块。
说到函数其实我们目前已经接触到了不少的系统函数,比如我们使用最多的打印输出函数、比如计算字符串长度的函数、比如清除列表数据的函数,等等。
print("我是你使用的第一个系统函数")
print(len("hello"))
print(max(3, 4))
输出结果:
我是你使用的第一个系统函数
5
4
上面举的这几个例子都是我们前面使用过的函数,当然还有很多我们没有列举出来。
Python中的函数分类
1、 内置函数
所谓内置是提前导入到解释器中的系统函数,比如print()、len()、max()
更多请看官方文档:https://docs.python.org/3/library/functions.html
2、 非内置的系统函数
比如math库的相关函数、random库的相关函数等等,使用时需要提前导入,例如使用math库的相关函数,需在文件中导入math库
import math
3、第三方函数
比如用来做数据分析统计的函数、游戏开发相关函数,由第三方团队或个人开发提供
4、自定义函数
我们用户自己写的函数称为自定义函数,接着我们将开始讲如何自定义我们自己的函数。
自定义函数
语法格式
def 函数名(参数列表):
函数体
举例,我们自己写一个类似于max()的自定义函数,比较两个数的大小,并打印较大的数
def my_print_max(x, y):
if x > y:
print(x)
else:
print(y)
my_print_max即函数名,x、y即函数参数,多个参数使用逗号隔开。
函数的调用
函数写好之后,需要供别人使用,例如我们使用(调用)上面的比较大小的函数
my_print_max(3, 5)
输出结果:
5
函数参数
函数参数分为形参和实参,定义函数时的参数为形参,调用函数时传入的参数为实参。
例如在定义my_print_max函数时的x和y为形参,告诉使用者这里有两个参数需要传入,在调用函数时my_print_max(3, 5)中的 3和5为实参,是传入的真实参数。
参数的几种类型
在讲参数类型之前,我们先写一个函数,这个函数可以帮人自动生成一段自我介绍。
1.必须参数
举例:定义一个函数,可以自动生成自我介绍
def auto_intro(name, age):
print("大家好,我叫%s,今年%d岁,请多多关照!" % (name, age))
auto_intro('张三', 18)
输出结果:
大家好,我叫张三,今年18岁,请多多关照!
必须参数要求实参的个数、传入的顺序与形参保持一致
2.默认参数
举例:定义一个函数,可以自动生成自我介绍
def auto_intro2(name, age, sex = 0) :
sex_str = "男" if sex == 0 else "女"
print("大家好,我叫%s,今年%d岁,性别%s,请多多关照!" % (name, age, sex_str))
auto_intro2('张三', 18) # sex使用默认值
auto_intro2('李四', 20, 1)
输出结果:
大家好,我叫张三,今年18岁,性别男,请多多关照!
大家好,我叫李四,今年20岁,性别女,请多多关照!
我们可以看到有了默认参数之后,调用函数时默认的参数可以不用传,但是实参的传入顺序仍然要和形参保持一直。
3.命名参数
命名参数的出现是为了解决实参的传入顺序必须和形参保持一直的问题,我们可以这样来传参:
auto_intro2('王五', sex=1, age=18)
输出结果:
大家好,我叫王五,今年18岁,性别女,请多多关照!
传参时指定参数的名称,这样就可以不用按形参的顺序传参了,这种传参方式称为命名参数。
4.不定长参数
有时候一个函数可能需要处理非常多的参数,就像我们上面写的函数一样,一个人除了姓名、年龄、性别外,还有身高、学历、专业、婚姻等多种信息,这些参数无法一一定义,为了解决类似这样的问题,python采用了不定长参数。
语法格式:
def 函数名(paramet1, *parameter2):
函数体
def 函数名(paramet1, **parameter2):
函数体
*parameter2:带一个星号的参数会将多个参数存储到一个元组中。
**parameter2:带两个星号的参数会将多个参数存储到一个字典中。
- 下面我们举例说明,函数定义:
def auto_intro3(name, age, *parameters) :
print("大家好,我叫%s,%d岁,请多多关照!" % (name, age))
print("我还有其他信息:", parameters)
函数调用:
auto_intro3('judy', 1, 'boy', '10kg')
输出结果:
大家好,我叫judy,1岁,请多多关照!
我还有其他信息: ('boy', '10kg')
我们可以看出parameters会将多余的参数'boy'、'10kg'存到了一个元组里。
- 下面我们换成两个星号的不定参数:
def auto_intro4(name, age, **parameters) :
print("大家好,我叫%s,%d岁,请多多关照!" % (name, age))
print("我还有其他信息:", parameters)
函数调用:
auto_intro4('judy', 1, sex='boy', weight='10kg')
输出结果:
大家好,我叫judy,1岁,请多多关照!
我还有其他信息: {'sex': 'boy', 'weight': '10kg'}
两个星号的不定长参数,在传入参数的时候要注意使用命名参数的形式,这样才会被以键值的形式存到字典中。
可变对象和不可变对象当做参数传递
首先我们来回顾一下目前我们学的哪些是可变数据类型,哪些是不可变数据类型。
不可变数据类型:
数字、字符串、元组
可变数据类型:
列表、字典、集合(可变的集合)
由于我们在参数传递时,不可变的参数和可变的参数会有不同的情况发生,所以我们需要在这里一起讨论这个问题。
可变对象当参数传递举例:
list_test = [1, 2, 3]
def func_test(li):
li[0] = 100 #li和list_test是同一个对象
print('li的内存地址',id(li))
func_test(list_test)
print('list_test的值', list_test)
print('list_test的内存地址', id(list_test))
输出结果:
li的内存地址 4492832584
list_test的值 [100, 2, 3]
list_test的内存地址 4492832584
我们发现把可变对象当参数传递时,li和list_test内存地址相同,也就是同一个对象,并没有创建新的对象,所以修改li就相当于是修改了list_test。
不可变对象当参数传递举例:
x = 1
def func_test2(y):
y = y + 1 #y和x不是同一个对象
print('y的内存地址', id(y))
func_test2(x)
print('x的值', x)
print('x内存地址', id(x))
输出结果:
y的内存地址 4491801696
x的值 1
x内存地址 4491801664
我们发现把不可变对象当参数传递时,x和y的内存地址不同,也就是重新创建了新的对象y,所以修改y的值并不影响x的值。
关于内存这块我们前面讲列表list的时讲了一个关于内存的有趣问题,不知你还记得否?只有我们了解了这些内存知识,在当一些现象发生时我们才不会觉得奇怪。AI方向的同学关于内存方面的知识了解即可。
函数返回值
很多时候我们希望一个函数通过计算得出某个结果,同时我们能够拿到这个结果。
例如我们上面举的例子,比较两个数的大小,我们不仅需要能够比较出来(打印出来),我们还需要得到其中较大的一个,那么这时候就需要返回语句。
举例说明:把我们自定义的my_print_max函数做如下修改:
def my_max(x, y):
if x > y:
return x
else:
return y
函数调用:
max_num = my_max(1, 2)
print("我们拿到了两个数中较大的一个:", max_num)
输出结果:
我们拿到了两个数中较大的一个: 2
所以我们注意到函数要想有返回值,可以使用return语句来实现。
return语句语法特点:
- 函数中使用return语句,可以将值返回
- 函数中使用return语句,return之后的代码不会被执行。
- return语句后不加参数,返回的值是None
变量的作用域
变量的作用域即变量在程序中起作用的范围。根据作用范围分为局部变量和全局变量。
局部变量
- 定义在函数内部的变量
- 形参和实参都属于局部变量
- 外部无法访问(使用)局部函数
- 局部变量如果和全局变量同名,局部变量优先被访问
全局变量
- 定义在函数外部的变量
- 同一个模块中都可以访问
- 函数内要改变全局变量的值,使用 global进行申明
- 全局变量一般在定义常量时使用(例如我们前面讲过的math模块中的pi、e常量)
1、举例:全局变量使用范围
b = '大校长' # 全局变量
def fun_grade1():
t1 = '一年级班主任' # 局部变量
print(b, '我要打小报告') # 可以访问全局变量
print(t1, '我要打小报告')
fun_grade1()
输出结果:
大校长 我要打小报告
一年级班主任 我要打小报告
在全局变量所在的模块中都可以访问全局变量,就像校长管着整个学校,哪个学生有问题都可以向校长反馈。
2、举例:局部变量使用范围
b = '大校长' # 全局变量
def fun_grade1():
t1 = '一年级班主任' # 局部变量
def fun_grade2():
t2 = '二年级班主任' # 局部变量
print(b, '我要打小报告') # 可以访问全局变量
print(t2, '我要打小报告')
#print(t1, '我要打小报告') # 无法访问局部变量t1
fun_grade2()
输出结果:
大校长 我要打小报告
二年级班主任 我要打小报告
在函数fun_grade2中无法访问fun_grade1函数中定义的局部变量t1。就像二年级的学生有问题可以找二年级的班主任和校长,一年级的班主任管不了。
3、举例:修改全局变量
b = '大校长' # 全局变量
def fun_grade3():
global b # 函数内修改全局变量,使用global申明
b = '大Boss'
print(b)
fun_grade3()
print('很明显我被修改成了:', b)
输出结果:
大Boss
很明显我被修改成了: 大Boss
三年级的学生给大校长起了个外号叫"大Boss",修改全局变量就如同给校长起外号一样,我们一般不轻易这样做,因为这样并不安全。
4、举例:局部变量和全局变量同名
a = 1
def fun1():
a = 2
print(a)
fun1()
print(a)
输出结果:
2
1
局部变量和全局变量同名,函数内部优先访问局部变量
lambda 表达式和匿名函数
匿名函数顾名思义是没有名称的函数,在python中使用lambda定义匿名函数。
lambda创建匿名函数语法格式:
lambda 参数列表: 表达式
lambda创建匿名函数特点:
- 表达式就是函数体,所以函数体不能太复杂
- 定义的函数的返回值就是表达式的值,不需要return语句
- 使用场景一般是把匿名函数当参数传递
举个例子:
fun2 = lambda x, y: x + y
print(fun2(1, 2))
输出结果:
3
以上我们定义了一个匿名函数,参数为x和y,返回x加y的值。这个例子不是很好,既然是匿名函数,我们再给他取个名字叫fun2显得有些多余。
匿名函数的使用场景一般是当做参数传递,下面我们以此来举例,使用lambda匿名函数找出列表中的偶数:
list1 = list(range(1, 11))
list2 = list(filter(lambda x: x % 2 == 0, list1))
print(list2)
输出结果:
[2, 4, 6, 8, 10]
以上的例子中filter是一个过滤函数,lambda匿名函数则作为过滤的条件:能被2整除,list1作为被过滤的对象。
函数的递归
递归函数是指直接或间接的调用函数本身,简单说就是自己调用自己的函数。
递归包含的代码逻辑:
1、终止递归的条件
2、对每次递归计算的值进行处理
我们尝试使用递归来计算0-100的整数和,在循环那一章我们讲过使用for或while循环来实现,下面使用递归来看看有什么不同。
def fun_sum(n):
if n < 1: # 终止递归的条件
return 0
else:
return n + fun_sum(n - 1) # 对每次递归计算的值进行处理(这里是相加)
print(fun_sum(100))
计算结果:
5050
递归还有很多使用场景,比如计算阶乘等等,我们自己去尝试,最后总结一下递归的特点:
- 递归来源于数学中的归纳法
- 递归可以用简单的代码实现复杂的逻辑
- 递归对计算机的性能及资源消耗较大
本章作业
1、模拟系统函数pow(),自己写一个my_pow()函数
2、使用递归计算n的阶乘,即输入参数n,计算出n!
本章总结
在我们学习函数之前,我们编写的程序都是分散在文件中的,这样至少有两个问题,一个是代码无法复用,第二个代码不易于维护,有了函数完美的解决了这些问题,好了,本章就到这里,我是猪弟爸爸,这里我会持续更新人工智能自学内容,有问题请关注我的公众号zhudipapa,我会统一在公众号下方回复,我们下节见。