day10-函数的介绍,参数以及返回值
函数的介绍
1.什么是函数
ps : 函数就是存放代码的容器
具备某一功能的工具-->函数
事先准备工具的过程---> 函数的定义
遇到应用场景拿来就用---> 函数的调用
所以函数的使用原则:
先定义
后调用
2.为何要用函数
解决下述问题:
1.代码组织结构不清晰,可读性差
2.可扩展性差
3.如何使用函数
定义的语法
def 函数名(函数1,函数2,函数3...):
# 函数名 和 变量名的定义方式格式一样,但最好使用动词,
'''函数的文档注释'''
代码1
代码2
代码3
return 返回值
调用的语法
一.定义的阶段:
发生的事情
-
申请一个内存空间,将函数体代码放进去,
-
将函数内存地址绑定给函数名
强调:只检测语法
在python中的变量名受到了特殊照顾,会自作主张的将变量名对应的内存地址的那个值返回,一般一个变量名返回的就是一个内存地址
所以调用函数名返回的是一个函数名对应的内存地址
例子:函数和变量返回的都是内存地址,但待遇不一样
def login(): # login = 函数的内存地址
print(1111)
print(2222)
print(3333)
x = 10 # x = 10 的 内存地址
print(x) # 10(本质是和函数一样的,返回内存地址,但python会自动给你将返回内存地址变成对应的那个值再返回)
print(login) # 0x0000029F7BE4A0D8
二.调用函数
语法:变量名加括号===>变量名()
1.先通过函数名定位到函数的内存地址
2.函数内存地址() ===> 触发函数体代码的运行
强调:调用函数才会执行函数体代码
login() # 执行一边函数体代码
login() # 又执行一遍函数体代码
这俩个函数名加括号调用函数执行函数体代码,充分的说明了函数的作用.
例子说明:
例子1:定义阶段只检测语法
def func():
print(1111 # 这是在定义阶段就检测的语法问题:SyntaxError: invalid syntax
print('我还没执行到这呢')
func()
例2:调用阶段,才执行函数体代码
def func():
print(1111)
x
print(123)
print('如果我执行了就代表函数定义阶段没问题')
func() # 报错的位置是line 76 NameError: name 'x' is not defined
例3和例4都是在强调函数分为俩个阶段,定义阶段和调用阶段
例3:
# =====按顺序先将bar的内存地址放到内存,再将foo的内存地址放到内存
def bar():
print('from bar')
def foo():
print('from foo')
bar() # 执行到这是前面bar的内存地址已经有了
foo() # 所以调用的时候是没有问题的
例4:
==== 将 foo的内存地址先放到内存,再将bar的内存地址放到内存,再执行foo
def foo():
print('from foo')
bar()
def bar():
print('from bar')
foo()
你会发现也是没有问题的.按道理来说不应该是会报错嘛?因为foo内的bar前面还没被定义呢?
但你忘了一件事,那就是函数是分俩个阶段的,一是调用,二才是执行.此时你看看.你执行foo()前,是不是已经将bar内存地址申请好了,因为定义的时候就已经申请好了,所以你在之后调用才不会有问题
除非你在调用foo之后,再定义的bar,它才会找不到对应的内存地址
三.定义函数的三种方式
3.1无参
概念:无参表示的就是在括号后面没有的"变量名"或者对应的"变量值"的
例子:
def func():
print('xxxx')
func()
3.2 有参
概念:有参表示的就是在括号后面有"变量名"或者对应的"变量值"的
例子:
def func1(x):
print(x)
func1(1)
3.3 空
概念:表示的是函数体代码为pass或者...的,只是保证执行时不会报语法错误,一般用于程序的设计搭建
def login():
pass # 也可以是 ...
login()
应用场景:
其实一个函数你要不要定义成一个无参的还是有参的,完全取决与你的函数体代码,里面的功能是不是要"写死"还是要"写活",参数就是一个可以变化的值,相当于变量,你不想写死,就给他一个可以变化的地方,而参数就是这么一个东西
所以说定义一个函数是不是有参无参,完全看函数体代码的需求
空函数表示的就是一个占位,表示这个函数定义阶段不会报错,并且可以执行,一般用于框架搭载时,先用pass占位,后面再来填代码的具体功能
例子:登入功能
def login():
inp_name = input("username>>>: ").strip()
inp_pwd = input("password>>>: ").strip()
if inp_name == "egon" and inp_pwd == "123":
print('login successful')
else:
print("username or password error")
login()
四:调用函数的三种形式
4.1 语句
len('hello')
4.2 表达式
res = len('hello') * 10 # 本质是函数的返回值进行运算
print(res)
4.3 可以当做参数传给另外一个函数
print(len('hello'))
像len()这种有返回值的函数一般都是作为具体的功能的作用,而print()这种没有返回值的一般都是作为语句的作用
函数的参数
总体分为俩大类:
1.形参:在函数定义阶段括号内指定的参数,称之为形式参数,简称形参--> 相当于变量名
2.实参:在函数调用阶段括号内指定的参数,称之为实际参数,简称实参 ---> 相当于变量的值
形参与实参的关系是:
在调用函数时,实参值会绑定给形参名,在函数调用完毕后解除绑定
细分的话
形参系列:
一:位置形参:在定义函数时,按照左到右的顺序依次定义的变量名,称之为位置形参
特点: 每次调用,必须被赋值
def func(x, y):
print(x)
print(y)
func(1,2,3)
func(1)
二:默认形参:在定义函数时,就已经为某个形参赋值了.称之为默认形参
特点:调用函数时,可以不为默认形参赋值
注意:可以混用位置形参与默认形参,但是
-
位置形参必须在默认形参的前面
-
默认形参对应的值一般是一个不可变的类型
-
不管是位置形参还是默认形参都是在定义阶段就放到内存的
例子:当形参为一个可变类型时,我该怎么处理这个可变类型
def func(name, hobby, hobbies=None):
if hobbies == None:
hobbies = []
hobbies.append(hobby)
print(f'{name}:{hobbies}')
func('jkey', 'eat')
func('song', 'play', ['music'])
例子2:当一个默认形参的值为一个变量时,值会怎么变化
m = 1111
def func2(x, y, z=m):
print(x, y, z)
m = 2222
func2(1, 2)
是不是发现它不会变成2222,哈哈哈,就是因为在定义函数的时候就已经将z=m赋好值了,这又充分说明了函数的形参在定义阶段就已经绑定到内存空间了.而m=2222 的时候,函数以及定义好了,所以z=m还是=1111
二:位置实参:在调用函数时,按照左到右的顺序依次传入的值,称之为位置实参
特点:按照位置未形参赋值,一一对应
例子:
def func(name, age):
print(name)
print(age)
func('egon', 17)
三.关键字实参:在调用函数时,按照key=value的形式传值,称之为关键字实参
特点:可以打乱顺序,但是仍然能够指名道姓的为指定的形参赋值
def func(name, age):
print(name)
print(age)
func(age=18, name='jkey')
注意:可以混用位置参数与关键字参数,但是
- 位置实参必须在关键字实参的前面
- 不能为同一形参重复赋值
例子解释:
func('egon', age=18)
# func(age=18,'egon') # 报语法错误 line 41 error SyntaxError: positional argument follows keyword argument
def foo(x, y, z):
pass
# foo(1,y=2,3) # 语法错误 位置实参必须在关键字实参前面
# foo(1, y=2, z=3, x=4) # 不能给同一个形参重复赋值,TypeError: foo() got multiple values for argument 'x'
foo(1, y=2, z=3)
可变长参数系列
概念:可变长参数指的是在调用函数时,传入的实参个数不固定,对应着必须有特殊形式的形参来接收移除的实参
> * 和 ** 在 形参中 其实就是一种汇总,合并功能=============
而实参无非俩种,所以它对应的特殊形式的形参应该是:
1.溢出位置实参 ---> *
2.溢出的关键字形参 ====> **
1.在形参种的应用:会将溢出的位置实参合并成一个元组,然后赋值给紧跟其后的那个形参名-- 这个形参名一般为args
def func(x, *args): # *的功能是 将 溢出的 位置实参(也就是值) 绑定个args
print(x) # 1
print(args) # (2, 3, 4, 5)
func(1,2,3,4,5)
例子: 计算n个数之和
def my_sum(*args):
sum_num = 0
for i in args:
sum_num += i
print(sum_num)
my_sum(1,2,3,4,5)
2.**在形参种的应用:**会将溢出的关键字实参合并成一个字典,然后赋值给紧跟其后的那个形参名 -- 这个形参名一般为 kwargs
def func(x, **kwargs): # **的功能是将溢出的关键字实参合并成一个字典
print(x) # 1
print(kwargs) # {'a': 2, 'c': 3}
func(1, a=2, c=3)
========= * 与 ** 在实参中是一种打散功能==========
* 在实参中的应用: * 后可以跟被for 循环遍历的所有类型,*会将紧跟其后的那个值打散成位置实参
def func(x,y,z):
print(x)
print(y)
print(z)
func(*(11,22,33))
# func(*"hello") # TypeError: func() takes 3 positional arguments but 5 were given
func(*{'x':1,"y":2,'z':3}) # x y z for遍历出来的默认是key
** 在实参中的应用: ** 只能跟字典类型,**会将字典打散成关键字实参
def func(x, y, z):
print(x)
print(y)
print(z)
# func(**{'k1': 111, 'k2': 222}) # 相当于func(k2=222,k1=111) 报错:TypeError: func() got an unexpected keyword argument 'k1'
func(**{"x": 111, "y": 222, "z": 333}) # 111\n222\n333
* 和 ** 在形参和实参中的应用
def index(x, y, z):
print(x, y, z)
def wrapper(*args, **kwargs): # 这里的*和**是在形参中,功能是将wrapper的位置实参和关键字实参给汇和,合并
index(*args, **kwargs) # 而这里的* 和 ** 是在实参中,表示的功能是将汇合合并后的形参打散. 会将wrapper中传入的参数原封不动的传入到 index函数
# wrapper(1, 2, 3, 4, 5, a=1, b=2, c=3) # 所以该wrapper的实参参照的形参其实是index的形参规范. 所以这样传会报错:TypeError: index() got an unexpected keyword argument 'a'
# 所以要想调用wrapper就必须满足index的形参规范
wrapper(1, 2, 3) # 1 2 3
wrapper(1, y=2, z=3) # 1 2 3
了解:命名关键字形参:在*和**中间的形参称之为命名关键字形参
特点: 必须按照key=value的形似传值
例子:
def func(x, m=1, *args, y=222, z, **kwargs):
print(x)
print(m)
print(args)
print(y)
print(z)
print(kwargs)
func(1, 2, 3, z=4, a=1, b=2, c=3)
# func(1, 2, 3, 4, 5, 6) # 多余的 2, 3, 4, 5, 6 全部给了* 绑定给了args
# 所以要想该z的值就必须得以关键字实参的形式来传
func(1, 2, 3, 5, 6, z=4)
函数的返回值
函数的返回值可以放在 return
关键字的后面,后面表示的就是该函数的返回值
返回值可以是任何数据类型
返回值的应用:计算俩个人月薪高的年薪
def max2(x, y):
"""
一般的函数就应该让对应的返回值表示的有意义,本函数就是一个例子
:param x:
:param y:
:return:
"""
if x > y:
return x
else:
return y
res = max2(10, 20)
print(res * 12)
return
关键字的作用:当一个函数体代码在执行的时候(即被调用后),只要执行到return
就会立即结束函数体的代码,并且将之后的值返回
例子:
def func():
"""
当函数体内出现了 return时,执行到return时函数体代码会立即结束(不会往下执行了)
但是你在return之后还有代码也没有关系,不会报语法错误,但是并不会执行
:return: 111
"""
print('=====>') # =====>
return 111 # 从这之后的函数体代码就不会被执行了
print('=====>2')
return 222
func()
返回值的三种类型
def func1():
"""
当返回值 只有一个的情况下,返回的就是这个值的本身
:return: 111
"""
print(111)
return 111
def func2():
"""
当返回值 有多个的情况下, 返回的就是小元组
:return: ('name', 111, True)
"""
print(222)
return 'name', 111, True
def func3():
"""
当没有返回值 或者 只有一个 return 或者 return None 的情况下, 返回的都是None
:return:
"""
print(333)
return
本章主要主要介绍了函数的基本使用
需要注意的是函数的参数部分 记住形参有俩种加上俩个特殊形式(位置形参,默认形参,*和**),对应的实参也有俩种加上俩个特殊形式(位置实参,关键字实参,*和** ), 在形参时的 *和 ** 的的功能是汇合,而在实参时的 * 和 **表示的是打散.