第六天函数进阶

6.函数的参数
1.函数的参数
Python 中函数的参数个数非常灵活,可以是 0 ,可以是 1 个,其实参数也可以是多个。如果有多个参数,每个参数用英文逗号隔开。
我们最熟悉的内置函数 print(),就支持多个参数,括号内将多个要打印的值用英文逗号隔开即可。这样多个内容可以同时打印在一行,并用空格分开。
print('小牛', '小马', '小侯')
# 输出:小牛 小马 小侯

2.参数的类型
根据不同的需求,Python 中的参数按传入方式分为两种:

位置参数
关键字参数

3.位置参数
大飞老师要记录刚刚入园的小朋友们的四个主要信息,姓名、年龄、身高、体重,然后打印出来。要怎么做呢?

这时就需要构造一个函数,并依次定义好四个参数代表上面的四个信息,在调用的时候按照按既定位置传入参数就可以。

大飞老师构造了一个名叫 record 的函数,并将参数传入顺序定为:

姓名
年龄
身高
体重

现在要显示小红和小牛的欢迎信息,具体的代码实现如下:
def record(name, age, height, weight):
  print('欢迎小朋友们!')
  print('姓名:', name)
  print('年龄:', age)
  print('身高:', height, 'cm')
  print('体重:', weight, 'kg')

# 下面开始调用定义的函数,传入关键信息。
record('小红', 5, 110, 22)
record('小牛', 5, 112, 21)
# 输出:
# 欢迎小朋友们!
# 姓名: 小红
# 年龄: 5
# 身高: 110 cm
# 体重: 22 kg
# 欢迎小朋友们!
# 姓名: 小牛
# 年龄: 5
# 身高: 112 cm
# 体重: 21 kg

大飞老师在调用函数时,就用到了 位置参数,按位置,依次传入了 name、age、height、weight 四个参数纪录小红和小牛的信息。

第一次记录小新的信息,name 的值为 '小红',age 的值为 5,height 的值为 110,weight 的值为 22。

第二次记录风间的信息,name 为 '小牛',age 为 5,height 为 112,weight 为 21。


4.关键字参数

由于新入园的小朋友很多,要输入的四个信息里,有三个是数字,大飞老师有时候会弄混输入的顺序,另外之后检查自己的输入时,这么多数字也会看花眼,怎么办呢?

大飞老师使用了 关键字参数 来解决这个问题。

def record(name, age, height, weight):
  print('欢迎小朋友们!')
  print('姓名:', name)
  print('年龄:', age)
  print('身高:', height, 'cm')
  print('体重:', weight, 'kg')

record(age=5, weight=21, name='小马', height=109)
# 输出:
# 欢迎小朋友们!
# 姓名: 小马
# 年龄: 5
# 身高: 109 cm
# 体重: 21 kg

上面的代码中,大飞老师在调用函数时按照 参数名 = 值 的格式传入了参数,这种参数就叫作 关键字参数。

在位置参数中,我们需要记得定义函数时每个参数对应的位置,而关键字参数可以 无视位置。我们可以很轻松地给对应的参数赋值,并且能一眼看出各个参数所对应的含义,在以后检查我们的代码时,也就更加方便了。
注意*****位置参数可以和关键字参数一起使用,但要保证所有位置参数都在关键字参数前,否则程序会因为识别错乱而报错。

我们来试试,如果把关键字参数放在位置参数前面会出现什么:
def record(name, age, height, weight):
  print('欢迎小朋友们!')
  print('姓名:', name)
  print('年龄:', age)
  print('身高:', height, 'cm')
  print('体重:', weight, 'kg')

record('小马', 5, height=109, 21)
# 报错:SyntaxError: positional argument follows keyword argument (语法错误:位置参数被放到关键字参数后面了)
Python 在解析参数时,是优先考虑 位置参数 的,位置参数 必须先满足才能考虑其他参数。因此下面代码才是可行的:
def record(name, age, height, weight):
  print('欢迎小朋友们!')
  print('姓名:', name)
  print('年龄:', age)
  print('身高:', height, 'cm')
  print('体重:', weight, 'kg')

# 位置参数放到所有关键字参数前面
record('小马', age=5, height=109, weight=21)


练习:
小新要和妈妈大美一起去超市买东西,妈妈问小新有没有想吃的零食,此时小新的脑海里就浮现出了“小熊饼干”“果汁”“牛奶”“薯片”的样子,止不住流下了口水。你能写一个多参数的函数,帮助小新将他想吃的东西都打印出来给妈妈看吗?
要求:

按顺序打印出小熊饼干、果汁、牛奶和薯片;
调用 print_foods() 函数两次,第一次使用位置参数,第二次使用关键字参数。
提示:使用关键字参数时,一定要保证参数与值对应哦。

def foods(food1,food2,food3,food4):
  print(food1)
  print(food2)
  print(food3)
  print(food4)
foods('小熊饼干','果汁','牛奶','薯片')
foods(food1='小熊饼干',food2='果汁',food3='牛奶',food4='薯片')


5.默认参数
大飞老师发现,小朋友们中,除了妮妮是 4 岁,其他人都是 5 岁,那么为了减少重复输入,可不可以在函数中,设定年龄的默认值为 5,在碰到例外时,再手动输入其它年龄呢?

当然可以,我们使用 默认参数 就能帮大飞老师解决这个问题!

来看看下面的代码:
# 我们可以在定义函数时,设置年龄默认为 5
def record(name, height, weight, age=5):
  print('欢迎小朋友们!')
  print('姓名:', name)
  print('年龄:', age)
  print('身高:', height, 'cm')
  print('体重:', weight, 'kg')

# 调用函数时,遇到 5 岁的小朋友,可以不输入年龄
record('阿呆', 115, 25)
# 输出:
# 欢迎小朋友们!
# 姓名: 阿呆
# 年龄 5
# 身高 115 cm
# 体重 25 kg

# 输入妮妮的信息时,要在最后填上她的年龄
record('小妮妮', 105, 18, 4)
# 输出:
# 欢迎小朋友们!
# 姓名: 小妮妮
# 年龄: 4
# 身高: 105 cm
# 体重: 18 kg
你有没有发现,我们在定义 age 的默认值时,将 age 移到了参数的最后的位置。这是因为,默认参数的要求是一定要放在非默认参数的后面,否则程序会出现错误,我们来试试:
def record(name, age=5, height, weight):
  print('欢迎小朋友们!')
  print('姓名:', name)
  print('年龄:', age)
  print('身高:', height, 'cm')
  print('体重:', weight, 'kg')

# 报错:SyntaxError: non-default argument follows default argument(语法错误:非默认参数被放到默认参数后面了。)
其实仔细想想也能明白为什么默认参数要放在最后:如果像上面这样写是对的,那么在调用时就会产生歧义了:

比如我们输入 record('阿呆', 115, 25),由于输入时使用的是位置参数,程序就会优先按位置,以“一个萝卜一个坑”的方式理解:
姓名是 阿呆
年龄参数 age 的位置上,有个数字 115,应该是让我不使用默认参数 5 岁,使用非默认值 115 岁
身高 25 cm
体重……哎,主人,参数用完了?我要报错了!
虽然我们人类看这样解析很荒诞,但是机器是很笨的,只能一条一条死按规则和顺序来,不会像我们人一样变通,才会出现上面哭笑不得的情况。所以我们要照顾机器,将默认参数写在最后,这样机器发现输入的参数用完后,才会在最后寻找函数内部的默认参数。

所以,我们要将默认参数按照正确的顺序定义成 def record(name, height, weight, age=5): 后,再输入 record('阿呆', 115, 25),笨笨的机器以“一个萝卜一个坑”的方式理解时,就没有问题:
姓名是 阿呆
身高 115 cm
体重 25 kg
年龄……哎,主人,参数用完了?等等,主人在最后预留了默认值 5,那他一定就是 5 岁了!
这样就解析成功了!


6.函数的多个返回值

接下来,大飞老师要单独打印出小朋友们的名字展示,不需要其它信息,但想想有多少个小朋友,就要调用多少次 print(), 非常累,有没有什么好办法能帮到她呢?下面,我们就来学习一下更简单的方法。
和函数的参数一样,函数也支持 0 到多个返回值。

多个返回值也和多参数一样,放在关键词 return 后面并列,然后用英文逗号隔开。

这样,大飞老师就不用一次次地打印了,直接把小朋友的名字放在一个函数中,在 return 后并列就好啦。
def new_kids(kid1, kid2, kid3, kid4):
  # 多个值在 return 后并列,用英文逗号间隔开
  return kid1, kid2, kid3, kid4

print(new_kids('小红', '小牛', '小马', '小妮妮'))
# 输出:('小红', '小牛', '小马', '小妮妮')
代码中,我们将多个小朋友的姓名在 return 后并列了出来,最后函数的返回值是 ('小红', '小牛', '小马', '小妮妮')。
这种用英文括号包裹,里面用英文逗号分隔开的是元组——Python 中的一种基本数据类型。这里只需要了解一下元组这个概念即可,后面将会详细介绍。

练习:
知道了函数可以有多个返回值,来试试,将之前打印小新想吃的零食的函数简化,改成有多个返回值的函数,再打印出来吧。

要求:按顺序返回小熊饼干、果汁、牛奶和薯片。
def return_menu(food1, food2, food3, food4):
  return food1, food2, food3, food4

print(return_menu('小熊饼干', '果汁', '牛奶', '薯片'))

接下来,是重点知识——函数的变量作用域,这里是函数中非常容易出错的地方,很多同学在写函数时,都会因为变量作用域没有弄清楚而遇到莫名其妙的错误。这不,我们的大飞老师也遇到了一个头疼的问题,一起来看看是怎么回事~

7.函数的变量作用域

大飞老师想把之前的程序再优化一下,在欢迎文字里加入幼儿园和班级信息。班级信息写入了函数中,并在一开始加入了幼儿园信息。

然后她还要用新加入的信息介绍一下自己。她是怎么做的呢?

# 加入幼儿园信息
school = '桃李幼儿园'

def record(name, weight, height, age=5):
  # 函数内部加入班级信息
  class_name = '殷桃班'
  print(school + class_name + '欢迎小朋友们!')
  print('姓名:', name)
  print('年龄:', age)
  print('身高:', height, 'cm')
  print('体重:', weight, 'kg')

record('小新', 22, 110)
# 输出:
# 桃李幼儿园殷桃班欢迎小朋友们!
# 姓名: 小新
# 年龄: 5
# 身高: 110 cm
# 体重: 22 kg

# 到此为止,大飞老师很满意,然后她要介绍自己了。
print('我是' + school + class_name + '的大飞老师,大家多多指教!')
想一想,最后一行代码会打印出什么结果呢?
结果是,报错了,信息如下:
NameError: name 'class_name' is not defined(命名错误:class_name 名称没有被定义)
原本以为会顺利显示出“我是桃李幼儿园殷桃班的大飞老师,大家多多指教!”,没想到,却报错了,说 class_name 没有被定义。

为什么明明在函数中将 '殷桃班' 赋值给了 class_name,却没有作用?

我们接下来看一下运行结果为什么是这样。

在 Python 中变量分为两种: 全局变量 和 局部变量。
全局变量 在当前写的代码中一直起作用。全局变量 有效的范围,叫作 全局作用域。

局部变量 只在一定范围内有效,在范围外就无效了。

我们在第五关里已经说过,定义函数的代码块中,形成了一个封闭的“小团体”,这就是一个被限定的小范围。其中定义的变量,都是 局部变量 。局部变量 有效的范围,叫作 局部作用域。要和外界交流,除非将某个变量用 return 语句变成函数的返回值。

由于 全局变量 作用于整个环境,所以就算跑到自成一体的函数内部,也是有效的。而函数内部的 局部变量 出了函数外,离开自己的 局部作用域,就无效了。


# school 是全局变量,函数内外都有效
school = '桃李幼儿园'

def record(name, weight, height, age=5):
  # class_name 是局部变量,只在 record() 函数中有效
  class_name = '殷桃班'
  print(school + class_name + '欢迎小朋友们!')
  print('姓名:', name)
  print('年龄:', age)
  print('身高:', height, 'cm')
  print('体重:', weight, 'kg')

# 调用函数 record(),使用了全局变量 school,有效, class_name 在函数内部被访问到,有效
record('小新', 22, 110)
# 输出:
# 桃李幼儿园殷桃班欢迎小朋友们!
# 姓名: 小新
# 年龄: 5
# 身高: 110 cm
# 体重: 22 kg

# 离开 record() 函数,脱离 class_name 的局部作用域,下一行代码中已经找不到 class_name,于是报错。
print('我是' + school + class_name + '的大飞老师,大家多多指教!')
# 报错:NameError: name 'class_name' is not defined


那么,我们要怎么帮大飞老师修正这个错误呢?相信聪明的你一定想到了最简单的方法,将 class_name 移出函数外来定义,作为全局变量,这样函数内外都可以访问到 class_name 的值。
school = '桃李幼儿园' # 全局变量
class_name = '殷桃班' # 全局变量

def record(name, weight, height, age=5):
  print(school + class_name + '欢迎小朋友们!')
  print('姓名:', name)
  print('年龄:', age)
  print('身高:', height, 'cm')
  print('体重:', weight, 'kg')

# school 和 class_name 可以正常访问
print('我是' + school + class_name + '的大飞老师,大家多多指教!')
# 输出:我是桃李幼儿园殷桃班的大飞老师,大家多多指教!

那么,我们有没有别的方法,不用将 class_name 移出函数呢?当然有!只需要在函数内部局部变量前加上 global 关键字,把局部变量强制变成全局变量即可,我们来看个例子:
school = '桃李幼儿园'

def record(name, weight, height, age=5):
  global class_name # 表示 class_name 强制变成全局变量
  class_name = '殷桃班'
  print(school + class_name + '欢迎小朋友们!')
  print('姓名:', name)
  print('年龄:', age)
  print('身高:', height, 'cm')
  print('体重:', weight, 'kg')

record('小新', 22, 110)
print('我是' + school + class_name + '的大飞老师,大家多多指教!')
# 输出:我是桃李幼儿园殷桃班的大飞老师,大家多多指教!

加了 global 关键字后,相当于我们宣布,函数里的东西大家可以一起用。上面例子中,class_name 就成了全局变量,函数外也能使用,便不会再报错了。

下面,我们再来思考一个问题,全局变量名和局部变量名可以相同吗?

答案是可以。那这样的话会不会冲突呢?我们用代码实际运行一下。
school = '桃李幼儿园'

def welcome():
  school = '牛娃幼儿园'
  print('欢迎来到' + school)

welcome()
# 访问的 school 是函数内的局部变量
# 输出:欢迎来到牛娃幼儿园

print('欢迎来到' + school)
# 访问的 school 是全局变量
# 输出:欢迎来到桃李幼儿园

我们可以看到,如果局部变量和全局变量名称相同,那函数内部会优先访问局部变量,外部只会访问全局变量。有了函数这堵“围墙”的保护,两种变量被完美地隔开,互不干扰,在各自的作用域内生效。

全局变量和局部变量被函数的内外界限分离,各自为政。

但有没有一种方法,让他们俩停止“各自为政”呢?

这时候又要用到我们的 global 关键字了。当函数内部出现了一个与全局变量同名的变量, 使用 global 变量名 语句会全局生效。

我们来看下面这个例子吧:
school = '桃李幼儿园'

def welcome():
  # 下面出场的 school,被 global 强行变成全局变量
  global school
  # 全局变量 school 再次赋值
  school = '牛娃幼儿园'
  print('欢迎来到' + school)

welcome()
# 访问函数内的 school,是被修改后的全局变量
# 输出:欢迎来到牛娃幼儿园

print('欢迎来到' + school)
# 访问的 school 是被修改后的全局变量
# 输出:欢迎来到牛娃幼儿园

上面的代码中,welcome() 函数内部的 school 沿用了外部的全局变量,无论怎么变,都是全局生效。

总结一下:函数内是 局部作用域,函数外是 全局作用域。局部作用域 的变量是 局部变量,只能在该函数内使用,全局作用域 的变量是全局变量,一般情况下函数内外都可以使用。

如果想让局部变量变成全局变量也是可以的,只需在使用变量前加上一句 global 变量名 即可。


回顾:

我们进一步感受了 函数 的神奇,学习了 函数的多参数、函数的多个返回值 以及 函数的变量作用域。

函数的 参数 个数支持从 0 到多个,传入多个参数的方式分为 位置参数 和 关键字参数,请记住:位置参数要放在所有关键字参数之前。除此之外,我们还学习了 默认参数,在定义参数时默认参数一定要放在位置参数后。

函数中用 return 可以有多个返回值,需要注意的是,每个返回值要用 英文逗号 隔开。

最后我们重点学习了 函数的变量作用域,需要注意的是,如果局部变量与全局变量名称相同,函数内会优先访问局部变量。

那么,在什么情况下要在函数内加 global:

在函数外想要使用函数内的变量(全局作用域使用局部变量)。如果不加 global,则无法在函数外访问函数内的变量。
在函数内修改函数外的变量(局部作用域修改全局变量)。如果不加 global,只是在函数内定义了和全局变量同名的变量,两者不同。

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值