[函数的进阶]
1.内容大纲
- 形参角度:
- 万能参数。
- *的魔性用法。
- 仅限关键字参数(了解)。
- 形参的最终顺序。
- 名称空间。
- 全局名称空间,局部名称空间
- 加载顺序,取值顺序。
- 作用域。
- 函数的嵌套(高阶函数)。
- 内置函数 globals() locals()
- 关键字:nonlocal global。
2.详细内容
-
形参角度:
-
万能参数
如果我们在传参数时不很清楚有哪些,或者说给一个函数传了很多实参,考虑用动态参数也叫万能参数。 万能参数,即动态参数,分为两种:动态接收位置参数 *args,动态接收关键字参数**kwargs. #动态接收位置参数 *args *【动态接收位置参数 *args:*args,约定俗成在*后使用args,PEP8规范中规定就使用args (这里起到魔法效果的是 * 而不是args)】 *【函数定义时,*代表聚合,它将所有的位置参数聚合成一个元组,赋值给了args,*args可以接受所有的位置参数。】 *【总结:*args只可以接受多个位置参数,并且返回一个元组。不能接受关键字参数。】 def eat(a,b,c,d): print('我请你吃:%s,%s,%s,%s'%(a,b,c,d)) eat('蒸羊羔','蒸熊掌','蒸鹿邑','烧花鸭')#我请你吃:蒸羊羔,蒸熊掌,蒸鹿邑,烧花鸭 def eat(*args): print('我请你吃:',args) #返回一个元组 print('我请你吃:%s,%s,%s,%s'%args) eat('蒸羊羔儿','蒸熊掌','蒸鹿尾儿','烧花鸭')##只能接受位置参数,不能接受关键字参数 #结果: 我请你吃: ('蒸羊羔儿', '蒸熊掌', '蒸鹿尾儿', '烧花鸭') 我请你吃:蒸羊羔儿,蒸熊掌,蒸鹿尾儿,烧花鸭 #练习:传入函数中数量不定的int型数据,函数计算所有数的和并返回。 def my_sum(*args): sum = 0 print(*args)#1 2 3 4 5 print(args)#(1, 2, 3, 4, 5) for i in args: #计数思想 sum += i return sum print(my_sum(1,2,3,4,5))#15 #动态接收关键字参数: **kwargs *【动态接收关键字参数: **kwargs ,**kwargs接受所有的关键字参数,然后将其转换成一个字典赋值给kwargs这个形参。】 *【函数定义时:**将所有的关键字参数聚合到一个字典,将这个字典赋值给了kwargs】 *【总结:**kwargs只能接受多个关键字参数,然后将其转换成一个字典。不能接受位置参数。】 def fuc(**kwargs): print(kwargs) fuc(name = '太白',age = 18,sex = '男')#只能接受关键字参数,不能接受位置参数 #{'name': '太白', 'age': 18, 'sex': '男'} #动态参数的完整写法:万能参数:*args, **kwargs, *【如果一个参数设置了动态参数,那么它可以接受所有的位置参数,以及关键字参数,这样就会大大提升函数拓展性,针对于实参参数较多的情况下,解决了一一对应的麻烦。】 def fuc(*args,**kwargs): print(args) print(kwargs) fuc('蒸羊羔儿','蒸熊掌','蒸鹿尾儿','烧花鸭','烧雏鸡','烧子鹅',name = '太白',age = 18,sex = '男') #结果: ('蒸羊羔儿', '蒸熊掌', '蒸鹿尾儿', '烧花鸭', '烧雏鸡', '烧子鹅') {'name': '太白', 'age': 18, 'sex': '男'}
-
*的魔性用法。
*的魔性用法:函数中分为打散和聚合。函数外可以处理剩余的元素。 ###打散和聚合: 1.聚合:在函数定义时,*代表聚合 。在args前面加一个* ,那么args可以接受多个位置参数,并且返回一个元组.(**kwargs也是同理将多个关键字参数转化成一个字典返回),所以在函数的定义时: *起到的是聚合的作用。 2.打散:在函数调用时,*代表打散:在实参角度的位置参数-->(可迭代对象:字符串,列表。元组)前面加*,相当于把这些实参拆解成的一个个的组成元素当作位置参数,然后传给args 。 在实参角度的位置参数-->字典前面加** # 在函数的调用时,*代表打散。 def func(*args,**kwargs): print(args) # (1,2,3,22,33) print(kwargs) func(*[1,2,3],*[22,33]) # (1, 2, 3, 22, 33) # {} func(*'fjskdfsa',*'fkjdsal') # ('f', 'j', 's', 'k', 'd', 'f', 's', 'a', 'f', 'k', 'j', 'd', 's', 'a', 'l') # {} func(**{'name': '太白'},**{'age': 18}) # () # {'name': '太白', 'age': 18} ##1: 针对*args,在实参角度的位置参数--可迭代对象(字符串,列表,元组)前面加*,相当于把这些实参拆解成的一个个的组成元素当作位置参数,然后传给args '''你如何将三个数据(这三个数据都是可迭代对象类型)s1 = 'alex',l1 = [1, 2, 3, 4],tu1 = ('武sir', '太白', '女神',)的每一元素传给动态参数*args?''' s1 = 'alex' l1 = [1, 2, 3, 4] tu1 = ('武sir', '太白', '女神',) def fuc(*args): print(args) fuc(s1,l1,tu1)#('alex', [1, 2, 3, 4], ('武sir', '太白', '女神')) fuc(*s1,*l1,*tu1)#('a', 'l', 'e', 'x', 1, 2, 3, 4, '武sir', '太白', '女神') ##2: 针对**kwargs,在实参角度的位置参数---字典前面加**,将字典变为关键字变量,再由**kwargs变为字典 ## def func(**kwargs): print(kwargs) dic1 = {'name': '太白', 'age': 18} dic2 = {'hobby': '喝茶', 'sex': '男'} func(**dic1,**dic2) #结果: {'name': '太白', 'age': 18, 'hobby': '喝茶', 'sex': '男'} ## def func(*args,**kwargs): print(args) print(kwargs) dic1 = {'name': '太白', 'age': 18} dic2 = {'hobby': '喝茶', 'sex': '男'} func(**dic1,**dic2) #结果: () {'name': '太白', 'age': 18, 'hobby': '喝茶', 'sex': '男'} func(dic1,dic2) #结果: ({'name': '太白', 'age': 18}, {'hobby': '喝茶', 'sex': '男'}) {} 3.函数外处理剩余的元素。 a,b = (1,2) print(a,b)#1 2 a,*b = [1,2,3,4] print(a,b)#1 [2, 3, 4] *rest,a,b =range(5) print(rest,a,b)#[0, 1, 2] 3 4 print([1, 2, *[3, 4, 5]])#[1, 2, 3, 4, 5]
-
仅限关键字参数(了解)
*【形参角度第四个参数:仅限关键字参数,形参的仅限关键字参数只接受实参的关键字传的参数】 *【仅限关键字参数是python3x更新的新特性,他的位置要放在*args后面,**kwargs前面,类似于位置参数,它与默认参数的前后顺序无所谓,它只接受关键字传的参数】 def func(a,b,*args,sex= '男',c,**kwargs,): #与默认参数的前后顺序无所谓,他的位置要放在*args后面,**kwargs前面 print(a,b) print(args) #元组 print(sex) print(c) #形参的仅限关键字参数,只能接受实参的关键字参数 print(kwargs) func(1,2,3,4,5,6,7,sex='女',name='Alex',age=80,c='666') #结果: 1 2 (3, 4, 5, 6, 7) 女 666 {'name': 'Alex', 'age': 80} def func(a,b,*args,d,sex = '男',**kwargs): #与默认参数的前后顺序无所谓,他的位置要放在*args后面,**kwargs前面 print(a,b) print(args) print(sex) print(d) print(kwargs) func(1,2,3,4,5,d=1,sex='女',name='alex',age=80)
-
形参角度的参数的顺序。
形参角度最终的顺序为: 位置参数,*args,仅限关键字参数,默认参数,**kwargs 或: 位置参数,*args,默认参数,仅限关键字参数,**kwargs # 位置参数必须在前面,即 :位置参数,默认参数 # *args 的位置? 【动态参数*args肯定不能放在位置参数前面,这样我的位置参数的参数就接收不到具体的实参了。】 【因为*args全部接收完了,所以动态参数必须在位置参数后面。】 【*args一定要在位置参数与默认值参数中间:位置参数,*args,默认参数】 #错: def func(*args,a,b,sex= '男'): print(a,b) func(1,2,3,4) # 错:args得到实参的前提,sex必须被覆盖了。 def func(a,b,sex= '男',*args,): print(a,b) print(sex) print(args) func(1,2,3,4,5,6,7,) #对: def func(a, b, *args, sex='男'): print(a, b) print(args) print(sex) func(1, 2, 3, 4, 5, 6, 7, sex='女') #结果: 1 2 (3, 4, 5, 6, 7) 女 # **kwargs 位置? def func(a,b,*args,sex= '男',**kwargs,): print(a,b) print(sex) print(args) print(kwargs) func(1,2,3,4,5,6,7,sex='女',name='Alex',age=80) # 1 2 # 女 # (3, 4, 5, 6, 7) # {'name': 'Alex', 'age': 80}
2.名称空间。
-
全局名称空间,局部名称空间
存放名字与值的关系’的空间------>命名空间
全局命名空间:代码在运行伊始,创建的存储“变量名与值的关系”的空间叫做全局命名空间;(py文件中,存放变量名与值的关系的一个空间叫做全局名称空间)
局部名称空间:当执行一个函数时,内存中会临时开辟一个空间,临时存放函数中的变量与值的关系,这个叫做临时名称空间【随着函数的执行的开始而创建,随着函数执行的结束而消失】
内置名称空间:内置名称空间存放的就是python源码给你提供的一些内置函数等拿来即用的特殊的变量:input,print,list等。
在python解释器开始执行之后, 就会在内存中开辟一个空间, 每当遇到一个变量的时候, 就把变量名和值之间的关系记录下来, 但是当遇到函数定义的时候, 解释器只是把函数名读入内存, 表示这个函数存在了, 至于函数内部的变量和逻辑, 解释器是不关心的. 也就是说一开始的时候函数只是加载进来, 仅此而已, 只有当函数被调用和访问的时候, 解释器才会根据函数内部声明的变量来进行开辟变量的内部空间. 随着函数执行完毕, 这些函数内部变量占用的空间也会随着函数执行完毕而被清空。
#名称空间;命名空间。 a = 1 b = 2 def func(): f = 5 print(f) c = 3 func() python分为三个空间: # 内置名称空间(builtins.py) # 全局名称空间(当前py文件) # 局部名称空间(函数,函数执行时才开辟)
2.加载顺序,取值顺序。
# 加载顺序: 内置名称空间 ---> 全局名称空间 ----> 局部名称空间(函数执行时) 在启动python解释器之后,即使没有创建任何的变量或者函数,还是会有一些函数直接可以用的比如abs(-1),max(1,3)等等,在启动Python解释器的时候,就已经导入到内存当中供我们使用,所以肯定是先加载内置名称空间,然后就开始从文件的最上面向下一行一行执行,此时如果遇到了初始化变量,就会创建全局名称空间,将这些对应关系存放进去,然后遇到了函数执行时,在内存中临时开辟一个空间,加载函数中的一些变量等等。所以这三个空间的加载顺序为:内置命名空间(程序运行伊始加载)->全局命名空间(程序运行中:从上到下加载)->局部命名空间(程序运行中:调用时才加载。
# 取值顺序(就近原则) 单向不可逆 LEGB原则 (从局部找时)局部名称空间 ---> 全局名称空间 ---> 内置名称名称空间 取值顺序就是引用一个变量,先从哪一个空间开始引用。这个有一个关键点:从哪个空间开始引用这个变量。 # 如果你在全局名称空间引用一个变量,先从全局名称空间引用,全局名称空间如果没有,才会向内置名称空间引用。 # 如果你在局部名称空间引用一个变量,先从局部名称空间引用。 # 局部名称空间如果没有,才会向全局名称空间引用,全局名称空间在没有,就会向内置名称空间引用。 所以空间的取值顺序与加载顺序是相反的,取值顺序满足的就近原则,从小范围到大范围一层一层的逐步引用。 input = '太白金星' def func(): input = 'alex' print(input) func() #alex input = '太白金星' def func(): input = 'alex' func() print(input)#太白金星
3.作用域。
# 两个作用域: 1.全局作用域 :内置名称空间 全局名称空间。在整个文件的任何位置都可以使用(遵循 从上到下逐⾏执行). 2.局部作用域:局部名称空间。在函数内部可以使用. # 全局作用域只能引用全局作用域的变量。全局变量要放在py文件的开头。 # 局部作用域可以引用全局作用域的变量, 局部作用域不能改变全局变量。 date = '周五' def func(): a = 666 print(date)#周五 print(a)#666 func() print(a)#NameError: name 'a' is not defined # 局部作用域不能改变全局变量。 局部作用域不能改变全局作用域的变量,当python解释器读取到局部作用域时,发现了你对一个变量进行修改的操作, 解释器会认为你在局部已经定义过这个局部变量了,他就从局部找这个局部变量,报错了。 #错【面试必考】 count = 1 def func(): count += 2 print(count) func() # UnboundLocalError: local variable 'count' referenced before assignment 变量“count” 在创建前引用 # 局部作用域可以引用父级作用域的变量,但是不能改变父级作用域的变量。 def func(): count = 1 def inner(): print(count) #1 inner() func() #错【面试必考】 def func(): count = 1 def inner(): count += 1 print(count) inner() func() #UnboundLocalError: local variable 'count' referenced before assignment
3.函数的嵌套(高阶函数)
# 例1: def func1(): print('in func1') print(3) def func2(): print('in func2') print(4) func1() print(1) func2() print(2) # in func1 3 1 in func2 4 2 # 例2: def func1(): print('in func1') print(3) def func2(): print('in func2') func1() print(4) print(1) func2() print(2) #1 in func2 in func1 3 4 2 # 例3: def fun2(): print(2) def fun3(): print(6) print(4) fun3() print(8) print(3) fun2() print(5) #3 2 4 6 8 5 # glbals() locals() 这两个内置函数可以反映作用域的内容,有助于我们理解作用域的范围。 globals(): 以字典的形式返回全局作用域所有的变量对应关系。 locals(): 以字典的形式返回当前作用域的变量的对应关系。
4.内置函数 globals() locals()
a = 1 b = 2 def func(): name = 'alex' age = 73 print(globals()) # 返回的是字典:字典里面的键值对:全局作用域的所有内容。 print(locals()) # 返回的是字典:字典里面的键值对:当前作用域的所有的内容。 print(globals()) # 返回的是字典:字典里面的键值对:全局作用域的所有内容。 print(locals()) # 返回的是字典:字典里面的键值对:当前作用域的所有的内容。 func()
5.关键字:nonlocal global。
-
3.总结[全部是重点]
- 参数:万能参数,仅限关键字参数(了解),参数的顺序,*的魔性用法:聚合,打散。
- 名称空间,作用域,取值顺序,加载顺序。
- globals locals
- 高阶函数:执行顺序。