目录
一、函数参数
昨天简单讲解了一些参数的知识点,现在我们拓展开来具体介绍,一下每一大类参数都分为形参和实参两种情况:
一、位置参数
位置形参
函数定义阶段括号内从左往右依次填写的变量名
def func1(a, b, c):pass
ps:当定义函数的时候,如果函数体代码足够简短并且只有一行可以不用换行直接跟在后面。
位置实参
函数调用阶段括号内从左往右依次填写的数据值
func1(1, 2, 3)
调用函数时的注意事项:
1、当我们在调用函数的时候,也需要在括号内加入对应的参数,并且这种情况下参数个数需要一样。
2、当我们给函数传参数的时候需要遵循一定的规律,把简短的放在前面,长的放在后面,简单的放在前面,复杂的放在后面。
3、在给函数传参数的时候也可以用关键字进行传参,由于关键字参数比位置参数要长,要复杂,所以使用的时候需要把它放在后面。
4、当我们在使用多种形式的参数给函数传参数的时候需要仔细判断,如果出现给一个形参传了两个参数会报错(不能多次赋值)。
5、调用函数的时候也可以使用变量来传参。
def func1(a, b):
print(a, b)
# func1(1, 2) # 按照位置一一对应传值
# func1(1) # 少一个不行
# func1(1, 2, 3) # 多一个也不行
# func1(b=1, a=2) # 关键字传参(指名道姓的传)
# func1(b=1, 2) # 关键字传参一定要跟在位置传参的后面 报错
# func1(2, b=1) # 可以
# func1(1, a=2, b=3) # 同一个形参在调用的时候不能多次赋值
name = 'jason'
pwd = 123
# func1(name, pwd) # 实参没有固定的定义 可以传数据值 也可以传绑定了数据值的变量名
func1(a=name, b=pwd) # 实参没有固定的定义 可以传数据值 也可以传绑定了数据值的变量名
二、默认参数(关键字参数)
所谓默认参数,本质上就是关键字参数,我们在定义的时候提前准备好一个默认的值,之后在调用的时候就可以选择不给参数,使用默认值。
关键字形参
当函数定义的时候,我们直接在参数中加入关键字,这个时候如果有别的位置形参,也要遵循短的简单的在前面,长的复杂的在后面的规律
def register(name, age, gender='male'):
print(f"""
--------学员信息----------
姓名:{name}
年龄:{age}
性别:{gender}
-------------------------
""")
register('jason', 18) # 输出的时候使用默认参数male
register('kevin', 28) # 输出的时候使用默认参数male
register('lili', 28, 'female') # 输出的时候使用实参输入的female
register('lili', 28, gender='female') # 输出的时候使用实参输入的female
关键字实参
关键字实参其实在将位置实参的时候一起讲解了,实际使用时候的规律可以参考上面的知识点。
三、可变长形参(可变长度的形参)
在我们定义函数的时候,如果想使用一个形参来接收传参时多余的一些参数,使函数不会在传参阶段出现报错,就可以使用可变长形参来接收多余的数据。下面我们介绍两种可变长形参:
1、在形参前面加一个*号
当我们给前面有一个''号的形参传多个位置参数的时候,这个''号会把所有的参数都接收然后放到一个元组中,再传给后面跟着的那个形参,我们通过打印就可以看到接收后的结果:
# def func1(*a):
# print(a)
# func1() # ()
# func1(1) # (1,)
# func1(1,2) # (1, 2)
# def func2(b, *a):
# print(a, b)
# func2() # 函数至少需要一个参数给到b
# func2(1) # () 1
# func2(1, 2, 3, 4) # (2, 3, 4) 1
ps:这里需要注意,如果函数的形参没有按照规定做到简单的短的放在前面,把可变长形参放到了前面,那么所有的位置参数都会被视为没用的数据被接收,其他后面的形参会接收不到实参,从而报错。
2、在形参前面加两个*号
两个*号跟在前面的形参表示接收所有多余的关键字参数,然后把这些关键字放入字典中,赋值给绑定的那个形参变量名。
# def func3(**k):
# print(k)
# func3() # {}
# func3(a=1) # {'a': 1}
# func3(a=1, b=2, c=3) # {'a': 1, 'b': 2, 'c': 3}
# def func4(a, **k):
# print(a, k)
# func4() # 函数至少需要一个参数给到a
# func4(a=1) # 1 {}
# func4(a=1, b=2, c=3) # 1 {'b': 2, 'c': 3}
# func4(a=1, b=2, c=3, x='jason', y='kevin') # 1 {'b': 2, 'c': 3, 'x': 'jason', 'y': 'kevin'}
由于两种形式的形参功能强大,使用频率很高,所以我们推荐使用以下两个变量名,方便判断:
*args
**kwargs
两者形式结合使用展示:
# def func5(*a, **k):
# print(a, k)
# func5() # () {}
# func5(1, 2, 3) # (1, 2, 3) {}
# func5(a=1, b=2, c=3) # () {'a': 1, 'b': 2, 'c': 3}
# func5(1, 2, 3, a=1, b=2, c=3) # (1, 2, 3) {'a': 1, 'b': 2, 'c': 3}
# def func5(n, *a, **k):
# print(a, k)
# func5() # 函数至少需要一个参数给到n
# func5(1, 2, 3) # (2, 3) {}
# func5(111,a=1, b=2, c=3) # () {'a': 1, 'b': 2, 'c': 3}
# func5(n=111,a=1, b=2, c=3) # () {'a': 1, 'b': 2, 'c': 3}
# func5(a=1, b=2, c=3, n=111) # () {'a': 1, 'b': 2, 'c': 3}
# func5(1, 2, 3, a=1, b=2, c=3) # (2, 3) {'a': 1, 'b': 2, 'c': 3}
四、可变长实参(可变长度的实参)
在可变长形参中我们得知一个号和两个号在形参中的作用,这两个符号在实参中也有两种不同的作用:
单个*号的作用:
在我们调用函数写入实参的时候,如果想把一个列表中的所有数据都传进去,我们可以直接在括号内写上列表的变量名然后在前面加个号,这样就可以把列表中的一个个值分别传入函数中。相当于使用for循环遍历数据按照位置参数传参给函数。当然号不仅仅列表能用字典元组字符串也是适用的。但是字典只能取到key。
# def index(a, b, c):
# print(a, b, c)
# l1 = [11, 22, 33]
# t1 = (33, 22, 11)
# s1 = 'tom'
# se = {123, 321, 222}
# d1 = {'username': 'jason', 'pwd': 123, 'age': 18}
'''将列表中三个数据值取出来传给函数的三个形参'''
# index(l1[0], l1[1], l1[2])
# index(*l1) # index(11, 22, 33)
# index(*t1) # index(33, 22, 11)
# index(*s1) # index('t','o','m')
# index(*se) # index(321 123 222)
# index(*d1) # index('username','pwd','age')
两个*号的作用:
两个号的作用范围比一个号少,只能被字典使用,当我们使用的时候,可以把字典内的一个个值直接传入函数中。
# def index(username, pwd, age):
# print(username, pwd, age)
# d1 = {'username': 'jason', 'pwd': 123, 'age': 18}
# index(username=d1.get('username'), pwd=d1.get('pwd'), age=d1.get('age'))
# index(**d1) # index(username='jason',pwd=123,age=18)
五、命名关键字参数(了解)
这里讲的是一种特殊情况下的关键字形参,先看例子:
def index(name,*args,gender='male',**kwargs):
print(name, args, gender, kwargs)
# index('jason',1,2,3,4,a=1,b=2)
index('jason', 1, 2, 3, 4, 'female', b=2)
我们可以看到由于长的复杂的需要放到后面,关键字形参需要安排到可变长形参中接收多余位置形参的参数的后面,接收多余关键字形参的参数的前面。当我们调用函数的时候就必须在写实参的时候用上关键字实参来表述,否则的话就会被判定成多余的位置实参被args形参直接接收走。
在我们编写代码的时候要尽量避免这种复杂的情况。
二、名称空间
名称空间就是我们存储变量名、函数名、方法名的一个空间。
我们把名称空间分为三种:内置名称空间、全局名称空间、局部名称空间。
内置名称空间
内置名称空间存在与解释器中,当我们运行解释器的时候就会产生。所有的方法名都存在这里。
全局名称空间
全局名称空间在运行py文件的时候才会产生,内部存放一些变量名、函数名等。目前来说py文件之间的全局名称空间不互通。
name = 'jason'
if name:
age = 18
while True:
gender = 'male'
def index():
pass
class MyClass(object):
pass
name\age\gender\index\MyClass这些都是在全局名称空间中的
局部名称空间
在我们编写函数的时候局部名称空间并不会产生,当我们调用函数的时候才会产生局部名称空间。内部有一些函数内的变量名或内部嵌套函数的函数名。
三、名称空间存活周期极其作用域
每个名称空间的存活周期和作用域都有所不同,为了方便理解,我们用表格进行对比:
存活周期
类型 | 存活周期 |
---|---|
内置名称空间 | 当我们运行解释器的时候就会产生内置名称空间,当我们关闭解释器的时候内置名称空间才会关闭,如果我们想修改内置名称空间的内容,也就意味着我们要去修改python解释器。 |
全局名称空间 | 当我们运行一个py文件的时候就会产生这个文件对应的全局名称空间,当这个py文件停止运行的时候就会关闭这个名称空间,如果我们想要修改全局名称空间中的内容直接在py文件中修改即可。 |
局部名称空间 | 局部名称空间在我们调用函数的时候会产生,当函数运行结束就会自动关闭,如果函数内嵌套有其他函数,那么这个局部名称空间不会函数调用运行结束后关闭,而是会在内部嵌套的函数也运行结束后才关闭。 |
作用域
类型 | 作用域 |
---|---|
内置名称空间 | 因为来自解释器,所有内置名称空间的作用域是全局有效 |
全局名称空间 | 因为全局名称空间来自py文件,所以通常情况下一个全局名称空间只对应一个py文件 |
局部名称空间 | 局部名称空间值能作用到函数内部的代码 |
四、名字的查找顺序
ps:这里需要注意,名称的调用和函数一样,需要先定义后使用,否则报错
在不同的名称空间下查找变量名有不同的查找顺序:
内置名称空间
当我们在内置名称空间中调用变量、方法的时候,会直接在内置名称空间中查找,如果没有就直接报错。\
全局名称空间
当我们在全局名称空间调用变量、方法的时候,会先在全局名称空间中查找。如果没有就会去内置名称空间中查找,再找不到也会报错。
局部名称空间
这里我们拿一个特殊例子来进行说明,当我们在一个函数的内部嵌套一个函数,在内部的函数中调用变量、方法的时候,会现在它自己对应的那个局部名称空间内部的局部名称空间中查找,如果没有就去外部的局部名称空间中查找,如果还没找到就去全局名称空间中查找,再没找到就是去内置名称空间中查找,最后还没找到的话就会报错。
关于查找顺序的举例:
1、互相没有关系的几个函数生成的局部名称空间并不能互通
def func1():
name = 'jason'
print(age)
def func2():
age = 18
print(name)
# 这里会直接报错
2、局部名称空间嵌套案例
x = '干饭了'
def func1():
x = 1
def func2():
x = 2
def func3():
print(x)
x = 3
func3()
func2()
func1()
这里是运行流程讲解:
这里如果把print(x)和x=3的运行顺序互换就会输出三,如果把x=3删了,就会输出func3外层中的x=2的结果
当我们不清楚调用的是哪个名称空间中的变量的时候,需要根据程序运行的流程进行分析,然后就可以得到准确的答案。
五、作业
判断下列money的值是多少并说明理由 思考如何修改而不是新增绑定关系
money = 100
def index():
money = 666
print(money)
money = 100
def func1():
money = 666
def func2():
money = 888
func2()
print(money)