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))

 

posted @   新兵蛋Z  阅读(332)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示