python-函数作用域和特别的函数以及装饰器
文档字符串
help()函数 是python的内置函数
通过help()函数可以查询python函数的用法
语法:
help(函数对象)
help(print) 这是查看print函数的用法
文档字符串(doc str)
在定义函数时,可以在函数的内部编写文档字符串,文档字符串就是函数的说明
当我们编写了文档字符串是,就可以通过help()函数来查看函数的说明
文档字符串非常简单,其实直接在函数的第一行写一个字符串就是文档字符串
就是在函数添加多行注释
def lmk(*nums) : ''' hello # 这个就是函数的说明 ''' b = 0 for a in nums : b += a return b
# 也可以在函数名那里明确参数的类型 def lmk (*nums:int) -> int: ''' 这是一个计算任何数字的函数,会把这些数字相加 ''' k = 0 for e in nums : k += e return k help(lmk) # 可以在 形参后面明确这个形参是int 还是str 等,也可以在括号后面加 -> int # 这个意思是,这个函数会返回什么样类型的值,就可以简单的理解这个函数的用法 # 输出结果是 # Help on function lmk in module __main__: # lmk(*nums:int) -> int # -> int 这个函数的注释解释是最终会返回int型
作用域
作用域指的是比那辆生效的区域
在python中一共有两种作用域
全局作用域
全局作用域在程序执行时创建,在程序执行结束时销毁,就是在函数外定义的变量都是在全局作用域定义的
所有在函数外的区域都是全局作用域
在全局作用域中定义的变量在全局作用域定义的变量都属于全局变量,全局变量可以在程序的任意位置被访问
函数作用域
函数作用域在函数调用时创建,在函数调用结束时销毁
函数每次调用一次就会产生一个新的函数作用域,就是每调用一次就相当于又重新再函数里定义了一个只在函数作用域中生效的函数,并在函数执行完后销毁
在函数作用域中定义的变量,都是局部变量,只能在函数内部生效被访问
函数作用域中的变量可以和全局作用域中的变量共同使用,但是在全局作用域中吊用不了函数作用域中的变量,只能通过调用函数使用函数作用域中的变量
变量的查找
当使用变量时,会优先在当前作用域中寻找该变量,如果有,则使用,没有的话就向上一级作用域中寻找,以此类推知道找到位置
如果从函数作用域找到全局作用域都没找到的话就会抛出异常,nameError
a = 10 def lmk (): print('a is',a) lmk() # 这个例子是 在函数作用域中没有变量,因为没有函数的嵌套所以直接找到了全局作用域 所以输出的a是全局的 # 如果在函数内部定义了一个a变量,则输出的a是函数内部的变量,优先输出当前作用域的变量
a = 10 def lmk (): def kml(): b = 20 def lmk1(): c = 100 print(a,b,c) lmk1() kml() lmk() # 这个是函数嵌套三层 # 要先看最里面的函数 # 输出a,b,c 最里面的函数作用域只定义了一个c 没有定义a 和b 所以往外找,在往上一层函数作用域中找到了b # 没有找到a,所以再次往上找,所有函数作用域中都没有a,所以找到了全局作用域中,在全局作用域中定义了a # 所以输出结果是 10 20 100 # 最后提一点,在全局作用域中是不能使用函数作用域中定义的变量的值的
如果在全局作用域中定义了a 在函数作用域中也定义了a,并且希望全局作用域中的a改为函数作用域的a可以用global声明
global 全局变量名
a = 10
def lmk ():
a = 20
print(a)
lmk()
print(a) # 这个输出结果是 20 10
# 在函数内修改a没有改变全局作用域中的a
----------------------------------------------------------
a = 10
def lmk ():
global a # 声明在函数内部使用的a是全局变量,此时再去修改a时,就是在修改a的全局变量
a = 20 # 在函数中为变量赋值是都是为全局变量赋值
print(a)
lmk()
print(a) # 这个输出结果是 20 20 全局作用域中的a已经被改为 20了
也可以这么理解,在全局作用域中的变量可以在函数作用域中修改,不过要先声明要修改函数为全局变量
在声明好后再去定义变量,就相当于修改全局作用域的变量了,在global之前的变量依然属于函数作用域的变量
递归函数
- 递归函数就是在函数内部再次调用自己
def fn(): # 在函数内部再次调用了自己 fn() fn() # 这个就是递归函数,而且是无穷递归,和while true 差不多
- 递归是解决问题的一种方式,和循环很像
- 它的整体思想是将一个大的问题拆分成一个个小的问题,直到问题无法拆分时,再去解决问题
- 递归函数的两个要件
1.基线条件
问题可以被分解为的最小问题,当满足基线条件时,递归就不在执行了
2.递归条件
将问题继续分解的条件,直到无法分解后结束递归# 这个例子是创建一个递归函数求任意数的阶乘 def factorial(n) -> int: # 该函数用来求任意数的阶乘 # 参数: # n 要求阶乘的数字 if n == 1 : return 1 return n * factorial(n-1) print(factorial(20))
编程式函数
- 在python中函数是一种对象
- 对象都会具有以下特点
- 对象是在运行时创建的
- 能赋值给变量或作为数据结构的元素
- 能作为参数传递
- 能作为返回值返回
高阶函数
高阶函数至少要符合一下两个特点中的一个
接收一个或多个函数作为参数
将函数作为返回值返回
高阶函数就是在函数中调用其他的函数,达到一定的功能,
高阶函数不会使原先的变量改变,而是返回一个新的列表
# 创建一个判断偶数的函数 def fn1(n) : if n % 2 == 0: return n # 创建一个判断奇数的函数 def fn2(n) : if n % 2 != 0 : return n # 创建一个列表 l = [1,2,3,4,5,6,7,8,9,10] # 定义一个函数 def fn(lmk,lst): # 创建一个新的列表 new_list = [] # 对列表进行筛选 for n in lst : # 判断n的奇偶 if lmk(n): # lmk是一个形参 取决于用户调用哪一个参数 new_list.append(n) return new_list # 返回列表 print(fn(fn1,l)) # 一共写了三个函数,最后一个函数分别调用前面两个函数来完成不同的功能,这就是高阶函数
以上是创建了一个高阶函数供参考,还有一个函数适用于高阶函数
filter() 函数
可以从序列中过滤出符合条件的元素,保存到新的序列中
参数:
filter(函数,序列)
1,函数 根据该函数过滤(可迭代结构) 和刚才最后一个函数调用前两个函数一样的
2,需要过滤的序列(可迭代的结构)
返回值: 返回值返回的是过滤后的新序列
# 创建一个判断偶数的函数
def fn1(n) :
if n % 2 == 0:
return n
# 创建一个判断奇数的函数
def fn2(n) :
if n % 2 != 0 :
return n
# 创建一个列表
l = [1,2,3,4,5,6,7,8,9,10]
# 调用filter()函数
# 输出filter过滤后的序列,要转换成列表才能查看,要不然看到的是filter的可迭代的结构
print(list(filter(fn1,l))) # 输出结果是 "[2, 4, 6, 8, 10]" 偶数
print(list(filter(fn2,l))) # 输出结果是 [1, 3, 5, 7, 9] 奇数
匿名函数
lambda()函数表达式专门用来创建一些简单的函数,它是函数创建的又一种方式
语法:
lambda 参数列表 : 返回值
匿名函数一般都是作为参数使用,其他地方一般不会使用
# 创建一个列表 l = [1,2,3,4,5,6,7,8,9,10] # 创建一个判断偶数的函数 def fn1(n) : if n % 2 == 0: return n # 创建一个判断奇数的函数 def fn2(n) : if n % 2 != 0 : return n # -------------------------------------------------------- # 上面这两个函数可以用匿名函数创建 print(list(filter(lambda n : n % 2 == 0,l))) # lambda n : n % 2 == 0 这个就是判断参数是否是偶数 再用filter函数过滤 就完美了,就是这么用的 print(list(filter(lambda n : n % 2 != 0,l))) # lambda n : n % 2 != 0 判断是否为奇数 # 两个的输出结果 # "[2, 4, 6, 8, 10] # [1, 3, 5, 7, 9]
map()
map() 函数可以对迭代对象中的所有元素做指定的操作,然后将其添加到一个新的对象中返回
可以对序列中的每个元素进行操作
格式:
map(函数,序列)
# 创建一个列表
l = [1,2,3,4,5,6,7,8,9,10]
a = map(lambda n : n + 2 ,l)
print(list(a))
# 输出结果是 [3, 4, 5, 6, 7, 8, 9, 10, 11, 12] 每个元素都加2返回一个新的列表,而源列表不变
sort()
- 该方法用来对列表中的元素进行排序,从小到大
- sort()方法默认是直接比较列表中元素的大小
- 在sort()方法中可以接收一个关键字参数,key
- 格式
列表.sort(key=函数)
- key需要一个函数作为参数,当设置了函数作为参数
- 每次都会以列表中的一个元素作为参数来调用函数,并且使用函数的返回值来比较元素的大小
- 加入key这个参数时,列表中的元素先转换为函数的返回值,然后sort()方法再进行排序
l = ['11','2','3333','444','55555555','7777777777','666666666666666666'] l.sort() print(l) # 没有加入参数key,是默认按列表里元素的大小来排序的 # 输出结果 "['11', '2', '3333', '444', '55555555', '666666666666666666', '7777777777'] # ---------------------------------------------------------------------------------------------- # 加入key l = ['11','2','3333','444','55555555','7777777777','666666666666666666'] l.sort(key=len) print(l) # 加入了一个len函数,列表会先经过len函数,再通过sort方法进行排序 # 返回结果是 ['2', '11', '444', '3333', '55555555', '7777777777', '666666666666666666'] # 用这个方法会改变原序列的顺序
sorted()
该函数和sort()用法基本一致,但是sorted()可以对任意的序列进行排序
并且使用sorted()排序不会影响原来的对象,而是返回一个新的对象
sorted可以对任意序列进行排序,而sort只能对列表进行排序,两个的实现的功能都相同
l = ['11','2','3333','444','55555555','7777777777','666666666666666666'] print('排序前',l) sorted(l) print('排序后',l) # 输出结果是 #排序前 "['11', '2', '3333', '444', '55555555', '7777777777', '666666666666666666'] #排序后 ['11', '2', '3333', '444', '55555555', '7777777777', '666666666666666666'] # 可以看到没有改变但是打印sorted(l) 就可以看到新的序列了 # print(sorted(l)) 输出结果是 # ['11', '2', '3333', '444', '55555555', '666666666666666666', '7777777777'] # 加入key参数后 l = ['11','2','3333','444','55555555','7777777777','666666666666666666'] print(sorted(l,key=len)) # 输出结果是 # ['2', '11', '444', '3333', '55555555', '7777777777', '666666666666666666'] # 序列按照长度进行排序了
闭包
将函数作为返回值返回,也是一种高阶函数
这种函数我们也称为闭包,通过闭包可以创建一些只有当前函数能访问的变量,
就是之前在全局作用域中的变量,我们放到第一个函数作用域,然后再创建一个函数去访问第一个函数作用域,并且返回值是函数里的函数,也就是返回我们编写功能的函数作用域,而第一个函数作用域里的变量在全局作用域中就访问不到了
可以将一些私有数据藏到闭包中
def fn(): a = 10 #函数内部再定义一个函数 def inner(): print('inner',a) #将返回值 inner作为返回值返回 return inner r = fn() r() # r是一个函数是fn()后的返回函数,不用r接收fn()函数是看不到 inner函数的返回值的 # 这个函数是在fn()内部定义,并不是全局函数 # 所以这个函数可以访问到fn()函数内的变量 # 而在全局作用域中是没有a这个对象的 # 这个函数最后的输出结果是 inner 10 是函数内函数的返回值,在全局变量打印a是打印不出来的
怎样形成闭包
形成闭包的3要件
1,函数嵌套
2,将内部函数作为返回值返回
3,内部函数必须使用外部函数的变量# 这个例子是在闭包中定义一个求平均值的功能 def make_average(): # 创建一个列表用来保存数值 nums = [] # 创建一个函数,用来计算平均值 def average(n): # 将 用户传入的参数 n 添加到上层函数的列表中 nums.append(n) # 求平均值 用到 sum()函数,作用是列表中数的和 return sum(nums)/len(nums) return average # average 去接收make_average这个函数 average = make_average() # 往列表添加函数,打印平均值 print(average(10)) print(average(15)) print(average(20))
装饰器
装饰器也是一种高阶函数
我们在调用函数时,如果感觉函数的功能还不是很好用,可以为这个函数扩展一下,而我们扩展函数的功能就会修改这个函数
所以就有了装饰器
我们先定义一个函数实现 两个数相加
def jia(a,b): rest = a + b return rest
希望在函数计算前打印开始计算,计算结束后打印计算完毕
如果我们直接修改函数中的代码完成需求,会产生以下的问题
1,如果要修改的函数过多,修改起来会比较麻烦
2,不方便后期维护
3,并且这样做会违反开闭原则(ocp)程序的设计,要求开发对程序的扩展,要关闭对程序的修改
如果这个函数或方法是开放的,就不能去修改源代码,要不然会有很多困扰
对扩展开放对修改关闭
# 上面定义了一个两个数相加的函数 # 现在对这个函数进行扩展 # 原函数-- def jia(a,b): rest = a + b return rest ----------------------------------------------------------------------------------------- # 扩展后-- def kuozhan_jia(a,b): print('计算开始') c = jia(a,b) print('计算结束') return c print(kuozhan_jia(5,5)) # 以上就是再不修改源函数的前提下对原函数进行扩展 扩展后的结果是 # 计算开始 # 计算结束 # 10
上面这个是达到了基本的要求了,但是不够灵活,如果还有一个原函数也需要这样修改就要定义两个扩展函数了
# 可以创建一个函数,让这个函数自动帮我们生产函数 def jia(a,b): rest = a + b return rest def kuozhan_jia(old): # old 是要扩展的函数名 ''' 用来对其他函数进行扩展,是其他函数可以在执行前打印开始,执行后的打印结束 参数 old 要扩展的对象 ''' # 创建一个 新函数 ,我们每调一次上层函数就会创建一个新函数 def new_jia(*a,**b): # 这里的 *a是接收所有的位置参数,有就接收,没有就不接受,**b是接收所有的关键字参数,有就接收,没有就不接收 print('开始') # 调用被扩展的函数 c = old(*a,**b) # 这里的是调用函数,所以就是把上层函数接收的*a和**b解包,然后调用*a是元组,**b是字典 print('结束') # c是接收old这个函数运行的结果然后return 返回给new_jia这个函数 ,然后上层函数返回new_jia return c # 返回里层函数,因为实在函数作用域中返回的,所以全局作用域是看不到的,我们就返回给第一层函数 return new_jia # 调用装饰器 kuozhan_jia(jia(1,2)) # 这个kuozhan_jia就是装饰器 # 通过装饰器可以在不修改原来函数的情况下对函数进行扩展
上面这个装饰器一般不这么用
@kuozhan_jia def chang(a): b = len(a) return b print(chang('sdfkljsdfj')) # 通过@符号调用装饰器来对下面定义的函数进行修改 # 通常在定义函数时,在定义函数上一行来用装饰器进行扩展,然后函数就有了扩展功能 # 输出结果是 # 开始 # 结束 # 10 # 可以为一个函数指定多个装饰器 # 一般装饰器先在里面装饰然后最外边装饰全部的 # 函数会由内到外进行装饰
本博客所有文章是以学习为目的,如果有不对的地方可以一起交流沟通共同学习 邮箱:1248287831@qq.com!