Python——函数&作用域
我们前面学的都是面向过程式的编程(代码从上到下写,并运行),而函数式编程是将繁杂的代码通过整理特性进行规整。像图书馆一样,将小说、教学、外文等书籍进行分类。让编程人员或看代码人员很容易的查看该段代码的特性。并且可以重复的利用该段代码进行相同的操作、像图书馆书是公用的一样,谁都可以去看,这样又能便于观察又能重复使用,是日后主要使用的技术。
def test(): #设置一个函数名称
#test #注释函数作用
print('函数式编程' ) #函数程序处理逻辑
return 0 #定义返回值,如果没有定义将返回None
test() #调用
函数:
1. 可以在调用时,在括号内放入参数,里面有(位置参数,关键字参数,混合参数,默认参数,汇总参数)
2. 位置参数必须要在关键字参数前面。
3. 函数传的参数赋值中,只复制引用而不复制对象。
位置参数:
将参数一一对应,并传入到函数中:
def func(a, b, c):
print(a, c, b)
func(1, 2, 3) #将1=>a,2=>b,3=>c
关键字参数:
将特定的参数以赋值形式对应起来。
def func(a, b, c):
print (a, c, b)
func(a=1, b=2, c=8)
混合参数(位置参数与关键字参数)
不能写在位置参数的前面:
def func(a, b, c):
print (a, c, b)
test(3, b= 2, c=1 ) √
test(c=1, 2) X
默认参数:
调用函数的时候,默认参数非必须传递。
如果再没有定义其他,就以默认的来。
在定义默认值时:不可变类型随便传,但可变类型需要注意:
def test(x, y=1):
print(y)
test(2)
汇总参数:
1. *args:接收N个位置参数,转换成元组的形式。
def test1(x, *args):
print(x)
print(args)
test1(1, 2, 3, 4, 5, 6) & test1(1, *[2, 3, 4, 5, 6]) #这里的*后面的列表是将列表内容提取并重新赋值给元组,以元组形式输出。
同时允许将列表项逐个传递给函数,无论列表项的数量是多少。
user_list = [1, 2, 3, 4, 5, 6,]
print(user_list)
print(*user_list,111)
[1, 2, 3, 4, 5, 6]
1 2 3 4 5 6 111
2. **kwargs:把N个关键字参数,转换成字典的方式。
def test1(x,**kwargs):
print(x)
print(kwargs)
# test1(1,name='3',age=20)
test1(2, name = 'xuan',age=9)
test1(1, **{'name':"xuan", 'age':20}) #将字典内容提取,在重新赋值给函数内的kwargs
2
{'name':'xuan', 'age':9 }
1
{'name':'xuan', 'age':20 }
通过sep关键字参数来将里面的数值进行拼接起来
默认是使用空字符串进行拼接
print(1, 2, 3, sep='=')
joinPrint = {'sep': '=='} #性质是一样的
print(1, 2, 3, **joinPrint)
作用域:
在Python中,一个函数就是一个作用域,而所有的函数都挂靠在.py文件的总作用域中。
在这里写的代码分为:全局作用域和局部作用域
1. 全局作用域
- 要使用全大写形式定义变量名称,为了将局部变量做标识。
- 局部作用域全部挂靠在全局作用域内
1 2 3 4 5 6 | NAME = 'a1' def func(): name = 'a2' print (name) print (NAME) func() |
2. 局部作用域:
- 局部作用域可以调用全局作用域。局部和局部,全局和局部不能调用。
- 局部作用域之间无法调用,可以使用父作用域。
1 2 3 4 5 6 7 8 | def change_name(name): #一个函数叫做一个作用域, print ( 'before change' ,name) name = 'XB' print ( 'after change' ,name) name = 'xb' change_name(name) print (name) |
global:
在局部作用域中,进行全局作用域内容的查找,并可以修改。(只能修改可变类型,不可变类型为局部内重新赋值)
1 2 3 4 5 6 | NAME = 'a1' def func(): global NAME NAME = 'bbb' func() print (NAME) |
nonlocal:
在子局部作用域中,进行对父级局部作用域的内容查找不更改全局作用域内容,并可以修改。(同样修改可变类型,不可变类型为局部内重新赋值)
1 2 3 4 5 6 7 8 9 10 | NAME = 'a1' def func(): NAME = '111' def func2(): nonlocal NAME NAME = 'bbb' func2() print (NAME) func() print (NAME) |
返回值:
在函数中使用return来进行函数这个子作用域的返回值的关键字。其中:
1. 默认如果没有return,但定义了取值变量的话,默认为None。
1 2 3 | def papa(): print ( '111' ) a = papa() #a就是None |
2. 如果只有一个返回值,那么就返回相应的数值和数据类型。如果有多个返回值,那么将返回一个元组。
1 2 3 4 5 6 7 | def papa(): dic = { 'k1' : 'v1' } return dic #将返回字典类型 return { 'k1' : 'v1' },{ 'k1' : 'v1' },{ 'k1' : 'v1' } #将返回元组类型 a = papa() print (a) |
3. 返回值同样可以返回另一个函数的内存对象地址
1 2 3 4 5 6 | def a(): print ( '111' ) def papa(): return a #返回一个内存对象地址,返回后可以直接运行。 b = papa() b()lam |
lambda
用于表示简单的函数时,可以使用lambda来进行,其中需要注意的:
1. 只能用一行来表示lambda
2. 只能用参数传的值,而不能自己定义一个变量。
使用lambda默认就会有一个return。
1 2 | a = lambda : 1 + 1 #将1+1的结果返回到a中。 print (a()) |
lambda可以搭配三元运算使用:
1 2 | a = lambda : 1 if 1 + 1 = = 2 else 2 #判断如果1+1等于2的话,那么返回1,否则返回2 print (a()) |
其他:
1 2 | lis = lambda : [ i for i in range ( 10 ) if i % 3 = = 0 ] #循环1-10,判断其中哪些为3整除,添加到列表中,生成一个lambda。 print (lis()) |
闭包:
闭包是嵌套在函数中的函数,而闭包必须是内层函数对外层函数的变量(非全局变量)的引用。
为函数创建一个区域(内部变量供自己使用)为以后执行提供数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | #实例 li = [] def func(new_value): li.append(new_value) total = sum (li) return total / len (li) print (func( 1000 )) print (func( 2000 )) print (func( 5000 )) #那么问题来了,li是全局变量,我可以在全局随意更改。 li = [] def func(new_value): li.append(new_value) total = sum (li) return total / len (li) print (func( 1000 )) print (func( 2000 )) li.append( 120301 ) print (func( 5000 )) #得出来的结果就大不相同了。如何有解决办法,那就是把li放到函数的小作用域里。 def func(new_value): li = [] li.append(new_value) total = sum (li) return total / len (li) print (func( 1000 )) print (func( 2000 )) print (func( 5000 )) #这样一来,每次运行函数,就会新生成一个新的列表出来。那么值就没有变化了。接下来如何解决这个问题。闭包就来了。 def func(): li = [] def func1(new_value): li.append(new_value) total = sum (li) return total / len (li) return func1 func = func() print (func( 1000 )) print (func( 2000 )) print (func( 5000 )) #这样一来,即不会出现全局模式更改的问题,也不会担心列表重新生成的问题。这就是比好。<br><br> |
其中,下面一块区域叫做闭包:
1 2 3 4 5 | li = [] def func1(new_value): li.append(new_value) total = sum (li) return total / len (li) |
我们可以通过查看函数内是否有自由变量来侧面证明是不是闭包。
1 2 3 4 5 6 7 8 9 10 11 12 | def func(): li = [] def func1(new_value): li.append(new_value) total = sum (li) return total / len (li) return func1 func = func() print (func.__code__.co_freevars) #查看函数的自由变量 #('li',) print (func.__code__.co_varnames) #查看函数的局部变量 #('new_value', 'total') |
函数需要注意的点:
1. 根据数据类型的不同,有些是有返回值的,有些是没有返回值的。
1 2 3 4 5 | lis = [] def a(): return lis.append( '111' ) b = a() print (b) #由于列表形式的添加是没有返回值的,所以返回的是None |
1 2 3 4 5 | lis = '123' def a(): return lis + '321' b = a() print (b) #由于字符串是有返回值的,所以返回的是所需值 |
2. 需要判断是传入函数内存地址还是函数的return的值。
1 2 3 4 5 6 7 8 9 10 11 | def func(): print ( '1111' ) return 0 lis = [func,func,func] #这里传入的是函数的内存地址。不是执行结果。 for item in lis: print (item) #打印的是内存地址 ''' ''' |
1 2 3 4 5 6 7 8 9 10 | def func(): return 0 lis = [func(),func(),func()] #这里传入的是运行完func的返回值 for item in lis: print (item) #打印的是0 ''' 0 0 0 ''' |
3. 闭包问题
1 2 3 4 5 6 7 8 9 | def func(name): v = lambda x:x + name return v v1 = func( '武沛齐' ) v2 = func( 'alex' ) v3 = v1( '银角' ) v4 = v2( '金角' ) print (v1,v2,v3,v4) |
1 2 3 4 5 6 7 8 9 10 | result = [] for i in range ( 10 ): func = lambda : i # 注意:函数不执行,内部代码不会执行。 result.append(func) print (i) print (result) v1 = result[ 0 ]() v2 = result[ 9 ]() print (v1,v2) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | def func(num): def inner(): print (num) return inner result = [] for i in range ( 10 ): f = func(i) result.append(f) print (i) print (result) v1 = result[ 0 ]() v2 = result[ 9 ]() print (v1,v2) |
4. 实参与形参
传参的类型是否为可变类型,如果可变类型,那么实参也会一起改变,如果是不可变类型,那么就会重新创建一个方法内部变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | ###########传入不可变类型############ def func(a): a + = 1 print ( id (a)) a = 7 func(a) print ( id (a)) print (a) #判断ID输出是否一样,a输出的结果? ############传入可变类型################### def func(a): a[ 0 ] = 8 print ( id (a)) a = [ 1 , 2 , 3 ] func(a) print ( id (a)) print (a) #判断ID输出是否一样,a输出的结果? |
函数中的那些坑:
1. 函数中有定义一个空列表作为形参:
def a(b,lis=[])
问题:在没有传入新的列表当做实参时,将会使用定义好的空列表,那么将会导致多个没有定义的实参重复调用该空列表。这将导致数据内容不一致的问题。
1 2 3 4 5 6 7 8 9 10 11 12 | def b(a,lis = []): for i in range (a): lis.append(i * i) print (lis) b( 3 ) b( 3 ) b( 3 ) ''' [0, 1, 4] [0, 1, 4, 0, 1, 4] [0, 1, 4, 0, 1, 4, 0, 1, 4] ''' |
可以传参时传入一个自己的空列表,那么就将使用自己传入的空列表。
1 2 3 4 5 6 7 8 9 10 11 12 | def b(a,lis = []): for i in range (a): lis.append(i * i) print (lis) b( 3 ,[]) b( 3 ,[]) b( 3 ,[]) ''' [0, 1, 4] [0, 1, 4] [0, 1, 4] ''' |
如何解决这类问题,要么就删除空列表,要么就进行判断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | def b(a,lis = []): if lis: lis = [] for i in range (a): lis.append(i * i) print (lis) b( 3 ,[]) b( 3 ,[]) b( 3 ) b( 3 ) b( 3 ) ''' [0, 1, 4] [0, 1, 4] [0, 1, 4] ''' |
编写高效的函数
- 起一个容易被理解的函数名
- 函数内代码行数最少30行,最多200行
- 传参最好保持0~3个,最好不要超过6个。
- 用*来进行列表的输出
- 用**来进行拼接
- 编写纯函数的最常见的方法是避免在函数内部使用全局变量,并确保不与文件、互联网、系统时钟、随机数或其他外部资源交互。
- 函数具备其他任何对象都有的功能,可以把函数存储在变量中,作为参数传递,或者把它作为返回值使用。
- 应尽量让返回值的数据类型保持不变。
- 不要写返回值为None
指定返回参数
类型提示使用冒号来分割参数和变量的名称与类型。对于返回值,类型提示使用(->)分割def语句的闭合括号和类型。
# 函数类型提示。 提示的参数数据类型为int,返回的参数为str
def descNumber(number:int) -> str:
if number % 2 == 1:
return '1111'
elif number == 66:
return '2222'
else:
return '6666'
num : int = 44
print(descNumber(num))
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)