函数进阶
昨日回顾
初识函数 定义: 对动作或者功能的封装. 语法: def 函数名(形参): 函数体(return) 函数名(实参) 形参: 在函数声明的位置写的变量 1. 位置参数 2. 默认值参数. 实参: 在函数调用的位置给出的具体的值 1. 位置参数 2. 关键字参数 3. 混合 位置, 关键字 传参: 把实参传递给形参的过程 return: 返回, 程序如果运行到return, 函数结束运行. 1. 当我的函数不写return, 表示默认返回None 2. 函数中写return, 返回None 3. return 值, 有一个返回值 4. return 值1, 值2, 值3 多个返回值, 返回的是元组
今日内容
1. 动态传参(重点) *, ** *, ** : 形参: 聚合 位置参数* -> 元组 关键字** -> 字典 实参: 打散(##动态参数的另一种传参方式,第一种是直接传递多个位置参数或关键字参数) 列表, 字符串, 元组 -> * 字典 -> ** 形参顺序(重点): 位置, *args, 默认值, **kwargs 无敌传参 def func(*args, **kwargs): arguments参数 keyword-arguments关键字参数 pass ## 所谓的接收动态位置参数或者说接收动态关键字参数是指的实参(手动重点:位置,关键字),因为参数是从实参传递给形参,进而给到函数内部调用的 ##原本实参位置是接收位置参数或者关键字参数,然后传递给形参位置以供函数内部调用的,这里通过*加列表来打散成多个位置参数,传递给形参位置的动态位置参数*args.
同理实参位置通过**加字典来打散成多个 关键字参数,传递给形参位置的动态关键字参数**kwargs 2. 作用域和名称空间 名称空间: 用来存放名字(变量, 函数名, 类名, 引入的模块名)的 1. 全局名称空间: 我们在py文件中自己写的变量, 函数..... 2. 内置名称空间: 我们python解释器提供好的一些内置内容(print, input....) 3. 局部名称空间: 在我们执行函数的时候.会产生一个局部名称空间. 放的是: 函数内部的内容(变量, 函数,类...) 名称空间可能会有无数个, 局部名称空间而言. 相对是独立的.一般互不干扰 作用域: 1. 全局作用域: 内置+全局 2. 局部作用域: 局部 globals() 查看全局作用域 locals() 查看当前作用域 3. 函数的嵌套 在函数中声明函数 在内部函数中使用变量的时候, 查找顺序: 先找自己 -> 上一层 -> 上一层..全局 -> 内置 4. nonlocal和global关键字(重点) global: 在局部引入全局变量 nonlocal: 在局部...内层函数引入外层离他最近的那个变量.
一.函数参数--动态传参
def chi(zhushi, cai, fushi, tang, tiandian): print(zhushi,cai,fushi,tang,tiandian) chi("大碗大米饭", "火爆大头菜", "把子肉", "西红柿鸡蛋汤", "烤地瓜") chi("小碗大米饭", "火爆大头菜") # 参数不够 # 形参的顺序(重点): # 位置 *args 默认值 **kwargs # 以后写参数. 可以随意的进行搭配, 但是, 顺序不能串 # * 在形参位置. * 表示不定参数-接收的是位置参数 # 接收到的位置参数的动态传参: 都是元组 def chi(*food): # 在形参这里把传递过来的实参进行了聚合,聚合成了元组 print(food) chi("小米粥") chi("小米粥", "咸鸭蛋") chi("爆米花", "咸鸭蛋", "辣白菜") chi("小米粥", "爆米花", "咸鸭蛋", "蒜茄子") def func(*args, a, b, c): print(a, b , c, args) func(1,2,3,4,5,6,7, a=8, b=9,c=10) # **在形参表示动态传参-关键字参数 # 关键字动态传参接收到的是字典 def func(**kwargs): # 也是聚合. ** 聚合成字典 print(kwargs) # func(1,2,3,4,5,6,7) # 位置参数. 报错 func(a=1, b=2, c=3, haha="呵呵", good="not bad") # def func(**kwargs, gender="男"): # 报错 # print(gender, kwargs) func(a=5,b=3,c=6, gender="女") # 无敌传参 def func(*args, **kwargs): # 参数没有限制. 随便传递 print(args) print(kwargs) func(1,2,3,4, a=3,b=5) # 这里是形参 # * 聚合 def chi(*food): print(food) lst = ["胡萝卜", "大白菜", "大萝卜", "草", "果冻"] # for el in lst: # chi(el) # # chi(lst[0], lst[1], lst[2], lst[3], lst[4]) # 这里是实参 # * 打散 chi(*"你今天吃了些什么") # * 打散. 把列表, 元组, 字符串打散成位置参数进行传递 chi("胡萝卜", "大白菜", "大萝卜", "草", "果冻") def chi(**food): # 聚合, 聚合成字典 print(food) dic = {"主食":'面条', "副食":"土豆泥", "汤":"疙瘩汤"} dic1 = {} chi(**dic, 甜点="冰激凌") # 打散. 打散成关键字 chi(主食="面条", 副食="土豆泥", 汤="疙瘩汤", 甜点="冰激凌") # 形参(*/**后跟的是聚合的结果): # *->元组, **->字典 表示聚合 # 实参(*/**后跟的打散之前的): # *->列表,字符串,元组, **->字典 表示打散
之前我们说过了传参,如果我们需要给-个函数传参,而参数又是不确定的.或者我给一个函数传很多参数,我的形参就要写很多,很麻烦,怎么办呢.我们可以考虑使用动态参数.
形参的第三种:动态参数
形参的顺序问题
动态参数分成两种:1,动态接收位置参数 2,动态接收关键字参数
1.动态接收位置参数
首先我们先回顾一下位置参数,位置参数,按照位置进行传参
def chi(quality_ food, junk food): print("我要吃”,quality food, junk_ food) chi("大米饭”,"小米饭”)# "大米饭"传递给quality_ food,“小米饭”传递给junk_ food,按照位置传
现在问题来了.我想吃任意的食物.数量是任意的,食物也是任意的.这时我们就要用到动态参数了.
在形参的参数位置编写*表示接收任意内容
def chi(*food): print("我要吃”,food) chi("大米饭”,“小米饭") 结果: 我要吃('大米饭’,'小米饭')#多个参数传递进去.收到的内容是元组tuple
动态接收参数的时候要注意:动态参数必须在位置参数后面,否则位置参数永远接收不到参数程序会报错
# 报错的写法
def chi(*food, a, b): # 报错 print("我要吃",food, a, b) chi("大米饭”,“小米饭”, “黄瓜”, “茄子")
这时程序运行会报错.因为前面传递进去的所有位置参数都被food接收了. a和b永远接收不到参数
Traceback (most recent call last): File "/Users/sylar/PycharmProjects/oldboy/fun.py", line 95,in <module> chi("大米饭",“小米饭”,“黄瓜”, “茄子") TypeError: chi() missing 2 required keyword-only arguments: 'a' and 'b'
所以必须改写成以下代码:(#实参使用关键字参数给形参中的位置参数传值)
# 有限制条件的写法
def chi(*food, a, b): print("我要吃”,food, a, b) chi("大米饭",“小米饭",a="黄瓜", b="茄子") # 必须用关键字参数来指定
这个时候a和b就有值了,但是这样写呢位置参数就不能用了.所以我们要先写位置参数,然后再用动态参数
# 正确的形参顺序,实参传参没有限制
def chi(a, b, *food): #动态参数放在位置参数后面,这样就可以使用位置参数给形参的位置和动态参数传值了 print("我要吃",a, b, food) chi("大米饭",”小米饭", ”馒头”, “面条") # 前两个参数用位置参数来接收,后面的参数用动态参数接收
那默认值参数呢?(# 目的就是要找到实参能够使用位置参数给所有的形参传值而且形参的默认值参数还能生效的形参的排列顺序)
# 错误的写法
def chi(a, b,c='馒头',*food): # 动态位置参数是多个位置参数的集合,也是位置参数,带等号的默认值参数就应该在后面 print(a, b,C, food) chi("香蕉","菠萝") #香蕉 菠萝 馒头 () ==>默认值生效 chi("香蕉","菠萝",”葫芦娃") #香蕉 菠萝 葫芦娃 () ==>默认值不生效 chi("香蕉”,”菠萝”,“葫芦娃”,“口罩") # 香蕉 菠萝 葫芦娃 ('口罩',) ==>默认值不生效
我们发现默认值参数写在动态参数前面.默认值只有一种情况可能会生效.
# 正确的写法
def chi(a, b, *food, c=" 娃哈哈"): print(a, b, food, c) chi("香蕉","菠萝") #香蕉菠萝(娃哈哈默认值生效 chi("香蕉","菠萝",”葫芦娃") # 香蕉菠萝('葫芦娃',)娃哈哈默认值生效 chi("香蕉”,"菠萝", “葫芦娃”, “口罩") # 香蕉菠萝('葫芦娃’,‘口罩')娃哈哈默认值生效
这个时候我们发现所有的默认值都生效了.这个时候如果不给出关键字传参.那么你的默认值是永远都生效的.所以默认值应该写在动态位置形参的后面
顺序:位置参数,动态参数*,默认值参数
2.动态接收关键字参数
在python中可以动态的位置参数,但是*这种情况只能接收位置参数无法接收关键字参数.
在python中使用**来接收动态关键字参数
def func(**kwargs): print(kwargs) func(a=1, b=2, c=3) func(a=1, b=2) 结果: {'a': 1, 'b': 2, 'c': 3} #多个参数传递进去.收到的内容是字典dict
{'a': 1,'b': 2}
这个时候接收的是一个dict
顺序的问题,在函数调用的时候,如果先给出关键字参数,则整个参数列表会报错.
def func(a, b, c, d): print(a, b, C, d) #关键字参数必须在位置参数后面,否则参数会混乱 func(1, 2,c=3, 4) # 报错
所以关键字参数必须在位置参数后面.由于实参是这个顺序.所以形参接收的时候也是这个顺序.也就是说位置参数必须在关键字参数前面.动态接收关键字参数也要在后面
形参最终顺序(重点):
位置参数> *args >默认值参数> *kwargs
这四种参数可以任意的进行使用.
如果想接收所有的参数:
def func(*args, **kwargs): print(args, kwargs) # 分别是元组,字典 func("麻花藤" ,"马晕" ,wtf="胡辣汤") # ('麻花藤', '马晕') {'wtf': '胡辣汤'}
动态参数的另一种传参方式
上面的栗子中,在函数调用的时候,实参位置的多个位置参数给形参的动态位置参数接收,而实参位置的多个关键字参数给形参位置的动态关键字参数接收
即原本实参位置 是接收位置参数或者关键字参数,然后传递给形参位置以供函数内部调用的,这里通过将列表打散成多个位置参数,传递给形参位置的动态位置参数*args.同理实参位置通过将字典打散成多个关键字参数,传递给形参位置的动态关键字参数**kwargs.
def fun(*args): print(args) lst=[1,4,7] fun(lst[0],lst[1], Ist[2]) fun(*1st) #可以使用*把一个列表按顺序打散,等效于上面一行的写法(手动打散传递多个位置参数),这样写内部会自动帮我们打散 s =“臣妾做不到” fun(*s) #字符串也可以打散,(可迭代对象)
在实参位置上给一个序列,列表,可迭代对象前面加个*表示把这个序列按顺序打散成位置参数.
在形参位置上的*表示把接收到的位置参数组合成一个元组
如果是一个字典,那么也可以打散.不过需要用两个*
def fun(**kwargs): print(kwargs) dic = {'a':1, 'b':2} fun(**dic)
小结:
# 动态接收位置参数
def chi(*food): print("我要吃", food) # 我要吃 ('⼤大⽶米饭', '⼩小⽶米饭') print(type(food)) # <class 'tuple'> chi("⼤大⽶米饭", "⼩小⽶米饭") #多个参数传递进去. 收到的内容是元组tuple ==>动态位置参数的第一种传参方式:直接传递多个位置参数 chi(*["⼤大⽶米饭", "⼩小⽶米饭"]) # *[列表]:打散成位置参数,等效于上面的调用 ==>动态位置参数的第二种传参方式:*可迭代对象(在实参位置会将可迭代对象打散,
将其每一元素变成一个位置参数进行传递给形参上面的动态位置参数)
# 动态接收关键字参数 def he(**drink): print("我要喝", drink) # 我要喝 {'a': '⼤大⽶米饭', 'b': '⼩小⽶米饭'} print(type(drink)) # <class 'dict'> he(a="⼤大⽶米饭", b="⼩小⽶米饭") #多个参数传递进去. 收到的内容是字典dict ==>动态关键字参数的第一种传参方式:直接传递多个关键字参数 he(**{"a":"⼤大⽶米饭", "b":"⼩小⽶米饭"}) # **{字典}:打散成关键字参数,等效于上面的调用 ==>动态关键字参数的第二种传参方式:**字典(在实参位置会将字典打散,
将其每一个键值对变成一个关键字参数进行传递给形参上面的动态关键字参数)
函数的注释
文档注释:在函数定义的下一行,打出三引号,按enter键,会自动补全参数以及返回值
def chi(food, drink): """ 这里是函数的注释,先写一下当前这 个函数是干什么的,比如我这个函数就是-个吃 :param food: 参数food是什么意思 :param drink:参数drink是什么意思 :return:返回的是什么东东 """ print(food, drink) return "very good"
二.命名空间,局部命名空间,全局命名空间,作用域,加载顺序.
a = 10 lst = [1,2,3,4] # 内置函数 print("你好啊,我叫赛利亚") def chi(): a = 10 b = 20 # 如果不调用chi() chi中的a和b都不会创建 # 如果调用了chi() 会创建a,b chi() def chi(): a = 10 print(a) chi() print(a) # 报错,外层不能调用内层的变量 def print(b): pass a = 10 def chi(): print(a) chi() # 从全局去找局部 -> 找不到 # 局部去找全局 -> 可以找到 # 怎么查看全局和局部中的内容 a = 10 b = 20 def 今天又是星期五(): pass # 查看全局作用域中的内容 print(globals()) # globals 全局作用域: 内置+全局名称空间 ''' {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000000000265C080>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, => print, input open '__file__': 'D:/python_workspace_s18/day10 函数的进阶/04 名称空间和作用域.py', '__cached__': None, # 自己写的. 全局 'a': 10, 'b': 20, '今天又是星期五': <function 今天又是星期五 at 0x0000000001D42E18>} ''' def chi(): a = 20 b = 30 print(locals()) # 查看当前作用域中的内容 print(globals()) # alex,老男孩儿, 武sir, 全局作用域中的内容 chi() print(locals()) # 这里的当前作用域是全局 print(globals()) # 全局
在python解释器开始执行之后,就会在内存中开辟-个空间, 每当遇到一个变量的时候,就把变量名和值之间的关系记录下来,但是当遇到函数定义的时候,解释器只是把函数名读入内存,表示这个函数存在了,至于函数内部的变量和逻辑,解释器是不关心的.也就是说-开始的时候函数只是加载进来,仅此而已,只有当函数被调用和访问的时候,解释器才会根据函数内部声明的变量来进行开辟变量的内部空间.随着函数执行完毕,这些函数内部变量占用的空间也会随着函数执行完毕而被清空.
def fun(): a =10 print(a) fun() print(a) # a不存在了已经..所以会报错
我们给存放名字和值的关系的空间起一个名字叫:命名空间.我们的变量在存储的时候就是存储在这片空间中的.
命名空间分类:
1.全局命名空间-->我们直接在py文件中,函数外声明的变量(包括函数名本身不包括函数内部的)都属于全局命名空间
2.局部命名空间-->在函数中声明的变量会放在局部命名空间
3.内置命名空间-->存放python解释器为我们提供的名字, list, tuple, str, int这些都是内置命名空间
加载顺序:
1.内置命名空间
2.全局命名空间
3.局部命名空间(函数被执行的时候)
取值顺序:
1.局部命名空间
2.全局命名空间
3.内置命名空间
a=10 def func(): a=20 print(a) func() # 20
作用域:作用域就是作用范围,按照生效范围来看分为全局作用域和局部作用域
全局作用域:包含内置命名空间和全局命名空间.在整个文件的任何位置都可以使用(遵循从上到下逐行执行).局部作用域:在函数内部可以使用.
作用域命名空间:
1.全局作用域:全局命名空间 +内置命名空间
2.局部作用域:局部命 名空间
我们可以通过globals()函数来查看全局作用域中的内容,也可以通过locals()来 查看局部作用域中的变量和函数信息
注意:locals:查看当前(locals所在的位置的)作用域中的内容,视它的具体位置而定,可能是局部,也可能是全局
a =10 def func(): a= 40 b=20 def abc(): print("哈哈") print(a, b) # 这里使用的是局部作用域 40 20 print(globals()) #打印全局作用域中的内容 {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x10316f550>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/tt.py', '__cached__': None, 'a': 10, 'func': <function func at 0x101ecee18>} print(locals()) #打印局部作用域中的内容 {'abc': <function func.<locals>.abc at 0x103514f28>, 'b': 20, 'a': 40} print(locals()) #打印全局作用域中的内容 {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x10306f550>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/tt.py', '__cached__': None, 'a': 10, 'func': <function func at 0x101ecee18>} func()
三.函数的嵌套
def outer(): def inner(): print("我的天哪") print("还可以这样写???") inner() outer() # 结果 # 还可以这样写??? # 我的天哪 # inner() # 在全局不能找到局部的内容 def func1(): print("1") def func2(): print("2") def func3(): print("3") print("4") func3() print(5) print("6") func2() print("7") func1() # 结果 # 1 # 6 # 2 # 4 # 3 # 5 # 7 # 全局变量一般是不能随意的修改的(##因为或许其他的函数还在调用,而你修改了) a = 10 def func(): # 慎用. global a # global 表示从全局把一个变量引入到局部, 后面使用的a都是全局变量 a += 10 # ?? a = a + 10 # 现在的a是全局的, 你现在试图改全局变量 print("里面的打印",a) func() print("外面的打印", a) # 改变之后的 20 a = 10 def func(): def inner(): # 慎用. global a # global 表示从全局把一个变量引入到局部, 后面使用的a都是全局变量 a += 10 # ?? a = a + 10 # 现在的a是全局的, 你现在视图改全局变量 print("里面的打印",a) inner() func() # 执行func函数内部调用inner函数,改变全局的a print("外面的打印", a) # 改变之后的 20 # nonlocal 在局部, 寻找离他最近的外层的一个变量 a = 50 def func1(): # a = 10 # 局部 def func2(): nonlocal a # 不找全局, global找全局 a += 10 # a = a + 10 原本python不让这么干 print("func2", a) func2() # 改变局部函数func1中的a = 20 print(a) # 20 func1() print(a) # 全局的还是50 # 如果没有nonlocal和global 查找的顺序: 自己, 上一层, 上一层, 上一层 def func0(): a = 50 def func1(): a = 10 # 局部 def func2(): nonlocal a # 不找全局, global找全局 a += 10 # a = a + 10 python不让这么干 print("func2", a) func2() print(a) func1() print(a) func0() a = 1 def fun_1(): a = 2 def fun_2(): nonlocal a a = 3 def fun_3(): a = 4 print(a) print(a) fun_3() print(a) print(a) fun_2() print(a) print(a) fun_1() print(a) # 实际应用 flag = False def login(): global flag uname = input("用户名:") upwd = input("密码:") if uname == "alex" and upwd == "123": flag = True else: flag = False def fatie(): if flag == True: print("可以发帖") else: print("滚去登录") login() fatie() fatie() fatie() fatie()
1.只要遇见了()就是函数的调用.如果没有()就不是函数的调用==>函数只有在调用的时候,内部的代码才会执行
2.函数的执行顺序
def fun1(): print(111) def fun2(): print(222) fun1() fun2() print(111)
# 结果
222
111
111
#函数的嵌套 def fun2(): print(222) def fun3(): print(666) print(444) fun3() print(888) print(33) fun2() print(555)
# 结果
33
222
444
666
888
四.关键字globa和nonlocal
均是引入外层的变 量到内层空间原本内层就能直接使用外层的变量但是不能对外层的变量进行修改(Python语言规定的),而使用global或nonlocal引入外层的变量后,之后在自己空间使用的都是引入的,不会也不能再在自己空间创建同名的变量,不管是赋值还是修改都是对外层原本变量的修改外层的也会同步的改变.
使用位置均是在内层局部空间引入外层空间的变量global引入的(只能)是全局的变量,
nonlocal引入的(只能)是外层局部的变量(由内到外/由近到远找外层局部空间的变量)
global: 在局部引入全局变量 nonlocal: 在局部...内层函数引入外层离他最近的那个局部变量. global:在局部命名空间引入全局命名空间中的变量 nonlocal:在局部命名空间引入外层局部命名空间中的变量
nonlocal找离它自身最近的局部
global只找全局的,全局没有报错;nonlocal只找局部的,局部没有报错
global
首先我们写这样一个代码, 首先在全局声明一个变量,然后再局部调用这个变量,并改变这个变量的值
a =100 def func(): global a #加了个global表示不再局部创建这个变量了,而是直接使用全局的a a=28 # 这里是修改全局的变量,如果不加上面一行是在局部空间创建一个新的变量 print(a) # 28 func() print(a) # 28
global表示.不再使用局部作用域中的内容及和改用全局作用域中的变量
lst = ["麻花藤”, “刘嘉玲”, ”詹姆斯"] def func(): lst . append("马云云")#对于可变数据类型可以直接进行访问,但是不能改地址.说白了.不能赋值 print(lst) # lst = ["麻花藤”, “刘嘉玲”, ”詹姆斯","马云云"] func() print(lst) # lst = ["麻花藤”, “刘嘉玲”, ”詹姆斯","马云云"]
nonlocal
nonlocal表示在局部作用域中,调用父级命名空间中的变量.
a=10 def func1(): a=20 def func2(): nonlocal a # 将a=20引入 a=30 # 改变引入的a print(a) func2() print(a) func1() # 结果: 加了nonlocal 30 30 不加nonlocal 30 20
再看,如果嵌套了很多层,会是一种什么效果:
a=1 def fun_1(): a=2 def fun_2(): nonlocal a # 引入a = 2 a=3 # 改变外层的a从2变成3 def fun_3(): a=4 # 新建 print(a) # 4 print(a) # 3 fun_3() # 4 print(a) # 3 print(a) # 2 fun_2() # 3 4 3 print(a) # 3 print(a) # 1 fun_1() # 2 3 4 3 3 print(a) # 1
这样的程序如果能分析明白.那么作用域, global, nonlocal就没问题了