# 1、为什么要用函数? # 2、函数的定义与调用 # 3、函数的返回值 # 4、函数的参数 # 5、命名空间和作用域 # 6、函数嵌套及作用域链 # 7、函数名的本质 # 8、函数的闭包
1、为什么要用函数?
假如len方法突然不能直接用了,现在有个需求是【计算'hello world'的长度】:
s1 = "hello world" length = 0 for i in s1: length = length+1 print(length)
现在又新增了一个需求,要计算另外一个字符串的长度,"hello China"。于是,这个时候你的代码就变成了这样:
s1 = "hello world" length = 0 for i in s1: length = length+1 print(length) s2 = "hello China" length = 0 for i in s2: length = length+1 print(length)
这会造成:
首先,之前只要执行len方法就可以直接拿到一个字符串的长度了,现在为了实现相同的功能我们把相同的代码写了好多遍 —— 代码冗余
其次,之前我们只写两句话读起来也很简单,一看就知道这两句代码是在计算长度,但是刚刚的代码却不那么容易读懂 —— 可读性差
于是乎,要是能像len一样将这段代码起个名字就好了,需要的时候直接“喊名字”就可以了。
2、函数的定义与调用
把上面求字符串长度的代码封装起来:
#函数定义 def mylen(): """计算s1的长度""" s1 = "hello world" length = 0 for i in s1: length = length+1 print(length) #函数调用 mylen()
函数名:函数名只能包含字符串、下划线和数字,且不能以数字开头。虽然函数名可以随便起,但我们给函数起名字还是要尽量简短,并能表达函数功能。
3、函数的返回值
将上面函数定义里的print改成return,在函数调用的时候看需求再print
#函数定义 def mylen(): """计算s1的长度""" s1 = "hello world" length = 0 for i in s1: length = length+1 return length #函数调用 str_len = mylen() print('str_len : %s'%str_len)
3.1 return关键字的作用
划重点:一旦遇到return,就结束整个函数。
返回值有几种情况:分别是没有返回值、返回一个值、返回多个值。
3.1.1 没有返回值
# return <=> return None <=> 没有return def ret_demo(): print(111) return print(222) ret = ret_demo() print(ret) ------------------------------------------------------------------------------------ def ret_demo(): print(111) return None print(222) ret = ret_demo() print(ret) ------------------------------------------------------------------------------------ def ret_demo(): print(111) print(222) ret = ret_demo() print(ret)
3.1.2 返回一个值
# 注意:return和返回值之间要有空格,可以返回任意数据类型的值 def ret_demo(): return 111 print(222) ret = ret_demo() print(ret) # 111
3.1.3 返回多个值
# 可以返回任意多个、任意数据类型的值 def ret_demo1(): '''返回多个值''' return 1,2,3,4 def ret_demo2(): '''返回多个任意类型的值''' return 1,['a','b'],3,4 ret1 = ret_demo1() print(ret1) # (1, 2, 3, 4) ret2 = ret_demo2() # (1, ['a', 'b'], 3, 4) print(ret2)
def ret_demo2(): return 1,['a','b'],3,4 #返回多个值,用一个变量接收 ret2 = ret_demo2() print(ret2) # (1, ['a', 'b'], 3, 4) #返回几个值,就用几个变量接收 a,b,c,d = ret_demo2() print(a,"->",b,"->",c,"->",d) # 1 -> ['a', 'b'] -> 3 -> 4
#序列解压一 >>> a,b,c,d = (1,2,3,4) >>> a 1 >>> b 2 >>> c 3 >>> d 4 #序列解压二 >>> a,_,_,d=(1,2,3,4) >>> a 1 >>> d 4 >>> a,*_=(1,2,3,4) >>> *_,d=(1,2,3,4) >>> a 1 >>> d 4 #也适用于字符串、列表、字典、集合 >>> a,b = {'name':'eva','age':18} >>> a 'name' >>> b 'age'
4、函数的参数
#函数定义 def mylen(s1): """计算s1的长度""" length = 0 for i in s1: length = length+1 return length #函数调用 str_len = mylen("hello world") print('str_len : %s'%str_len)
4.1 实参和形参
我们调用函数时传递的这个“hello world”被称为实际参数,因为这个是实际的要交给函数的内容,简称实参。
定义函数时的s1,只是一个变量的名字,被称为形式参数,因为在定义函数的时候它只是一个形式,表示这里有一个参数,简称形参。
4.2 传递多个参数
参数可以传递多个,多个参数之间用逗号分割。
def mymax(x,y): the_max = x if x > y else y return the_max ma = mymax(10,20) print(ma)
4.3 位置参数
4.3.1 站在实参角度:(顺序:位置参数,默认参数,关键字参数a = '')
对于一个形参只能赋值一次
# ①按位置传参 def mymax(x,y): #此时x=10,y=20 the_max = x if x > y else y return the_max ma = mymax(10,20) print(ma)
# ② 按照关键字传参 def mymax(x,y): #此时x = 20,y = 10 print(x,y) the_max = x if x > y else y return the_max ma = mymax(y = 10,x = 20) print(ma)
# ③ 位置、关键字形式混着用 def mymax(x,y): #此时x = 10,y = 20 print(x,y) the_max = x if x > y else y return the_max ma = mymax(10,y = 20) print(ma)
4.3.2 站在形参角度:(顺序:*args,默认参数,**kwargs)
def func(*args,default = 1,**kwargs): #形参顺序: (*args,默认参数,**kwargs) print(args,default,kwargs) func(1,2,3,default=2,a='aaaa',b='bbbbb') #实参顺序: (位置参数,默认参数,关键字参数a = '') 运行结果: (1, 2, 3) 2 {'a': 'aaaa', 'b': 'bbbbb'}
位置参数必须传值
def mymax(x,y): print(x,y) the_max = x if x > y else y return the_max #调用mymax不传递参数 ma = mymax() print(ma) #结果 TypeError: mymax() missing 2 required positional arguments: 'x' and 'y'
4.4 默认参数
# 默认参数的值是一个可变数据类型 # 每一次调用函数的时候,如果不传值,就公用这个数据类型的资源 def stu_info(name,sex = "male"): """打印学生信息函数,由于班中大部分学生都是男生, 所以设置默认参数sex的默认值为'male' """ print(name,sex) stu_info('alex') # alex male stu_info('eva','female') # eva female
# 默认参数的陷阱 def qqxing(k,d = {}): d[k] = 'v' print(d) qqxing(1) #{1: 'v'} qqxing(2) #{1: 'v', 2: 'v'} #新执行完的都添加到默认的dict里边 qqxing(3) #{1: 'v', 2: 'v', 3: 'v'}
4.5 动态参数
# *args:按位置传值多余的参数都由args统一接收,保存成一个元组的形式 def mysum(*args): the_sum = 0 for i in args: the_sum+=i return the_sum the_sum = mysum(1,2,3,4) print(the_sum)
# **kwargs def stu_info(**kwargs): print(kwargs) print(kwargs['name'],kwargs['sex']) stu_info(name = 'alex',sex = 'male')
参数总结
5、命名空间和作用域
5.1 命名空间
【依赖倒置原则】:向上依赖
【向下选择原则】:当我自己有的时候,我就不找我的上级要了;如果我自己没有,就一层一层往上级找(找级数最低的),如果内置的命名空间里也没有,则报错
多个函数应该拥有多个独立的局部名字空间,不互相共享
命名空间
## 内置命名空间 -- python解释器 # python解释器一启动就可以使用的名字存储在内置命名空间中 # 内置的名字在其冻结时期的时候被加载进内存里 ## 全局命名空间 -- 我们写的代码,但不是函数中的代码 #是在程序从上到下被执行的过程中依次加载进内存的 #放置了我们设置的所有变量名和函数名 ## 局部命名空间 -- 函数 #就是函数内部调用的名字 #当调用函数的时候,才会产生这个名称空间,随着函数执行的结束,这个命名空间就又消失了 -------------------------------------------------------------------------------- ----##三种命名空间之间的加载与取值顺序: #加载顺序: #内置命名空间(程序运行前加载)->全局命名空间(程序运行中:从上到下加载)->局部命名空间(程序运行中:调用时才加载) # 取值顺序: # 在局部调用:局部命名空间->全局命名空间->内置命名空间 x = 1 def f(x): x=3 print(x) f(2) # 3 print(10) # 10 # 在全局调用:全局命名空间->内置命名空间 x = 10 def f(x): print(x) f(2) # 2 print(x) # 10
5.2 作用域
作用域就是作用范围,按照生效范围可以分为全局作用域和局部作用域。
5.2.1 globals() 和 locals()方法
【全局作用域】: -- globals():永远打印全局的名字
包含内置名称空间、全局名称空间,在整个文件的任意位置都能被引用、全局有效。
【局部作用域】:-- locals():取决于位置,在哪儿哪儿就是本地
局部名称空间,只能在局部范围内生效 。
a = 1 b = 2 def func(): x = 'aaa' y = 'bbb' print(locals()) print(globals()) #全局的:永远打印全局的名字 func() 运行结果: {'y': 'bbb', 'x': 'aaa'} {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000219469AEF28>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'F:/Python/Project/All_Stacks/01 基础/day10/02.函数的命名空间.py', '__cached__': None, 'a': 1, 'b': 2, 'func': <function func at 0x00000219468A3E18>}
5.2.2 global关键字
a = 10 def func(): global a # 在函数(局部命名空间)中执行的是全局的变量a print('func中a的值:',a) a = 20 # 修改的是全局的a的值,生效 print('全局中a的值:',a) func() print('执行完func后全局中a的值:',a) 运行结果: 全局中a的值: 10 func中a的值: 10 执行完func后全局中a的值: 20
6、函数嵌套及作用域链
6.1 函数嵌套
内部函数使用外部函数的变量
def max2(x,y): m = x if x>y else y return m def max4(a,b,c,d): res1 = max2(a,b) res2 = max2(res1,c) res3 = max2(res2,d) return res3 ret = max4(23,-7,31,11) print(ret) # 31 -------------------------------------------------------------------------------- def f1(): def f2(): def f3(): print("in f3") # ⑤ print("in f2") # ③ f3() # ④ print("in f1") # ① f2() # ② f1() 运行结果: in f1 in f2 in f3
6.2 函数作用域链
def f1(): a = 1 def f2(): def f3(): print(a) f3() f2() f1() # 1 ------------------------------------------------------------------------------------ def f1(): a = 3 def f2(): a = 2 f2() # 并没有改变 f1函数中a的值 print('a in f1 : ',a) f1() # 3
6.3 nonlocal关键字
#定义: # 声明了一个上层的局部变量,作用域只到 上层第一个 有该变量(最低层),找上层中离当前函数最近一层的局部变量 # 声明了nonlocal的内部函数变量修改,会影响到当前函数最近一层的局部变量 #规则: # 1.外部必须有这个变量 # 2.在内部函数声明nonlocal变量之前不能再出现同名变量 # 3.内部修改这个变量如果想在外部有这个变量的第一层函数中生效 def f1(): a = 1 def f2(): nonlocal a a = 2 f2() print('a in f1 : ',a) # a in f1 : 2 f1()
6.4 综合示例
a = 1 def outer(): a = 1 #此处是局部变量 def inner(): a = 3 b = 2 print("inner中a的值:",a) print('inner') #此处只是定义,并没有调用 def inner2(): # global a #声明了一个全局变量,修改的是全局的a,不修改局部的 nonlocal a #声明了一个上层的局部变量,作用域只到 上层第一个 有该变量(最低层),找上层中离当前函数最近一层的局部变量 # 声明了nonlocal的内部函数变量修改,会影响到当前函数最近一层的局部变量 a += 1 #不可变数据类型的修改 print('inner2中的a:%d'%a) # print('inner2') inner2() inner() print('局部的a:%d'%a) outer() print('全局a:%d'%a) 运行结果: inner中a的值: 3 inner inner2中的a:4 局部的a:1 全局a:1
7、函数名的本质
函数名本质上:就是函数的内存地址
def func(): print(123) func2 = func ### 函数名可以赋值 func2() # 123
7.1 可以被引用
def func(): print('in func') f = func print(f) #<function func at 0x000001C6CF353E18>
7.2 可以被当做容器类型的元素
def func(): print(123) func2 = func # 函数名就是内存地址,可以赋值 l = [func,func2] # 函数名可以作为容器类型(如list,dict等)的元素 print(l) for i in l: # i <=> 函数名 i() # 调用函数 运行结果: [<function func at 0x0000025136343E18>, <function func at 0x0000025136343E18>] 123 123
7.3 可以当做函数的参数和返回值
def func(): print(123) def wahaha(f): f() # 执行func() return f # 函数名可以作为函数的返回值 qqxing = wahaha(func) # 函数名可以作为函数的参数 <=> qqxing = f qqxing() # 等价于执行 func() 运行结果: 123 123
8、函数的闭包
闭包发生在嵌套函数中:
# bar函数不需要传参数,就能获取到外面的变量,这是一个典型的闭包现象。 def foo(): name = "Andy" # 定义了一个foo函数内部的局部变量 def bar(): print(name) # 在bar函数内部引用了其外部函数foo的局部变量name return bar func = foo() # 相当于执行 func = bar func() # 相当于执行 bar() # Andy
8.1 闭包的定义
函数内部定义的函数,称为内部函数。这个内部函数只有使用了它外部函数的局部变量,即使外部函数返回了,这个内部函数还可以访问到外部函数的局部变量,这种现象就叫做闭包
。
8.2 闭包的本质
闭包:是由函数和与它相关的引用环境组合而成的实体。
像下面的 func
函数就不能称为闭包,因为它并没有使用包含它的外部函数的局部变量。
name = "Alex" # 定义了一个全局变量 def foo(): def bar(): print(name) # 在bar函数内部引用了全局变量name return bar func = foo() print(func.__closure__) # None
8.3 判断闭包函数的方法__closure__
#输出的__closure__有cell元素 :是闭包函数 def func(): name = 'eva' def inner(): print(name) print("inner.__closure__ : ",inner.__closure__) return inner f = func() f() #运行结果: (<cell at 0x00000215E4550C48: str object at 0x00000215E46F84C8>,) eva ———————————————————————————————————— #输出的__closure__为None :不是闭包函数 name = 'egon' def func2(): def inner(): print(name) print("inner.__closure__ : ", inner.__closure__) return inner f2 = func2() f2() #运行结果: None egon
8.4 闭包嵌套
def wrapper(): money = 1000 def func(): name = 'eva' def inner(): print(name,money) # eva 1000 return inner return func f = wrapper() i = f() i()