Python——函数(函数定义、函数返回值、global关键字、函数形参、函数实参、lambda匿名函数)

Python函数

在Python中,一切皆对象,同样函数也是对象,和int、bool等类型的对象处于同一地位(C语言中就不是这样的了)

函数的定义

定义函数的过程和其他类型对象的赋值定义过程的本质是类似的,定义函数就是创建了一个函数对象,并将该对象的引用保存到一个全局变量中,这个全局变量通常被称为函数名,只不过,与其他变量不同的是,该全局变量保存的 id 对应的内存空间的内容为一个代码块(即函数体代码)

在Python中,定义函数需要使用到关键字def
无论这个函数是否具有参数,都必须具有括号,否则会提示语法错误

def func():		#函数对象的引用保存在全局变量func中
    pass

print(func)		#打印函数对象

#输出结果:<function func at 0x000001CF468558B0>

函数定义时的一整个代码块和下面这个代码的本质相同

num = 10

在进行函数定义的时候,不会执行函数体中的代码

def func():
    r = math.radians(1)
    print(r)

func()

#输出结果:name 'math' is not defined

def func():
    r = math.radians(1)
    print(r)

#代码效果:毫无动静,正常结束

import math

def func():
    r = math.radians(1)
    print(r)

func()

#输出结果:0.017453292519943295

Python函数必须要先定义,再调用,否则会引发异常

func()

def func():
    pass

#引发异常:name 'func' is not defined

其实引发异常的本质和下面的这个代码是一样的,因为Python一切皆对象,函数也是对象

num += 1

num = 0

#引发异常:name 'num' is not defined

定义函数的时候可以指定形式参数的类型,但是如果调用时传入的实际参数类型和定义时的指定的类型不同,也不会有任何影响
指定形式参数的类型的作用在于在编写函数的时候编译器会根据指定类型进行代码提示,方便编写代码,仅此而已

def func(num:list):
    print(num)

func(123)

#输出结果:123

提示效果
在这里插入图片描述

定义函数的时候可以进行代码注释,当编写调用函数的代码时,解释器会有相应的提示消息出现

def func():
    'func函数的代码注释'

提示效果

也可以在help帮助中进行查询,注意查询的名称为__main__.XX
在这里插入图片描述

Python函数与其他Python对象具有相同的特点

Python中的函数支持嵌套定义,这为闭包的产生奠定了基础(想深入了解Python闭包的同学可以移步Python——闭包与装饰器

def out_func():         #外层函数
    def in_func():      #内层函数
        print(123)

    in_func()           #调用内层函数

out_func()  #调用外层函数

#输出结果:123

当然,一般情况下是不会单独地在一个函数中定义一个函数,然后在自己对新定义的函数进行调用,而是选择把这个新定义的函数进行返回,交由外部进行调用,即使用闭包

在函数内部定义的函数为一个局部变量

def out_func():
    def in_func():
        print(123)

    print(locals())     #打印局部变量及与其对应的对象

out_func()

#输出结果:{'in_func': <function out_func.<locals>.in_func at 0x000001733B984B80>}

由于函数名本质就是保存着函数对象引用的全局变量,所以Python中的函数对象也可以作为右值参与赋值运算,我们可以通过被赋值的变量调用该函数

def func():
    print(123)

func()

function = func     #将函数对象引用复制一份,保存在function中
function()

#输出结果:
"""
123
123
"""

当然,函数也可以作为实参传入另一个函数中被调用

def func1():
    print("func1函数被调用")
    print(123)

def func2(func):
    print("func2函数被调用")
    func()		#在func2内部调用输入的函数

func2(func1)	#将func1引用传入func1

#输出结果:
"""
func2函数被调用
func1函数被调用
123
"""

函数的返回值

如果一个函数没有任何返回值,会返回一个空值None,相当于在函数的最后有一个默认的代码"return None",如果只有一个return,后面没有任何东西返回,那依旧是返回None

def func():
    pass

re = func()
print(re)

#输出结果:None

def func():
    return None

re = func()
print(re)

#输出结果:None

def func():
    return

re = func()
print(re)

#输出结果:None

函数可以有多个返回值,这多个返回值会组成一个元组(本质上就是元组构成方式的一种)

def func():
    return 1, 2, 3

re = func()		#调用函数,得到返回值
print(re)

#输出结果:(1, 2, 3)

函数与global全局变量

函数内部可以直接使用外部的全局变量的数据

num = 10

def func():
    print(num)
    print('函数内部作用域中的局部变量及与其对应的对象:', locals())

func()

#输出结果:
"""
10
函数内部作用域中的局部变量及与其对应的对象: {}
"""

但是这样做,修改全局变量的引用是不被允许的(当然,从异常信息来看,编译器是认为这个局部变量未定义,而进行了使用)

num = 10

def func():
    num += 1    #试图修改全局变量的引用

func()

#引发异常:local variable 'num' referenced before assignment

顺带一提,如果在函数中先使用了同名变量,再定义了一个同名的局部变量,也会抛出这个异常

num = 10

def func():
    print(num)
    num = 0
    
func()

#引发异常:local variable 'num' referenced before assignment

如果在函数内部使用global,就可以借用已经存在的同名全局变量了(相当于全局变量异地登入,这个变量就是外部的同名全局变量,二者是同一个变量),此时就可以修改全局变量保存的引用

num = 10

def func():
    global num
    num += 1    #试图修改全局变量的引用

func()
print(num)      #查看调用函数后,全局变量保存的数据

#输出结果:11

在使用global关键字借用全局变量之前,不能再定义一个同名变量,否则会引发异常

num = 10

def func():
    num = 10
    global num
    
func()

#引发异常:SyntaxError: name 'num' is assigned to before global declaration

由于函数在调用的时候,函数体代码才会执行,所以函数内部的全局变量借用是在函数执行的时候进行的操作

从打印对应对象的引用计数的结果也可以看出

num = 10

print('函数定义之前的num引用计数', sys.getrefcount(num))

def func():
    global num
    print(num)

print('函数定义之后的num引用计数', sys.getrefcount(num))

#输出结果:
"""
函数定义之前的num引用计数 127
函数定义之后的num引用计数 127
"""

使用global关键字的时候,如果函数之前没有同名的全局变量,则可以实现函数中创建一个全局变量

def func():
    global num
    num = 10
    
func()		#由于函数的内部代码在定义时不会执行,所以必须要先调用func,才能创建全局变量num
print(num)

#输出结果:10

也可以使用global关键字使得函数成为一个全局变量

def out_func():
    global in_func
    def in_func():
        print(123)

out_func()      
in_func()       #调用全局变量in_func

#输出结果:123

函数的参数(实际参数和形式参数)

函数传参

Python的函数传参只有一种方式,为传址调用,无传值调用,具体来说,就是Python在传参的时候,是将变量保存的对象引用复制一份,交给形参保存

class C:
    pass

def func(arg):
    print(arg)	#打印传入的实例对象

ins = C()		#实例化一个对象
print(ins)		#打印这个实例对象
func(ins)

#输出结果:
"""
<__main__.C object at 0x000001CD5A34ACD0>
<__main__.C object at 0x000001CD5A34ACD0>
"""

实际参数

  • 位置参数
  • 位置参数是根据实参位置依次进行参数传递的
def func(a, b):
    print(a, b) 	

func(1, 2)     #1, 2均为位置参数

#输出结果:1 2
  • 关键字参数
  • 关键字参数是根据形参名称进行指定的参数传递的
def func(a, b):
    print(a, b) 

func(b = 1, a = 2)     #a, b均为关键字参数

#输出结果:2 1
  • 关键字参数不能写在位置参数之前
def func(a, b):
    print(a, b) 

func(b = 1, 2)     #b为关键字参数,2为位置参数,并且位置参数在关键字参数后

#代码效果:SyntaxError: positional argument follows keyword argument
  • 不能对同一个参数多次赋值,会引发异常
def func(a):
    print(a) 

func(1, a = 2)     #1为位置参数,a为关键字参数,都向同一个形参赋值

#输出结果:func() got multiple values for argument 'a'
  • 序列解包会优先于关键字参数进行赋值(本质可能是在调用传参的时候,由于位置参数必须在关键字参数前面,所以在传参的时候会自动进行位置调整)
def func(a, b):
    print(a, b)

func(a = 1, *[2])

#引发异常:func() got multiple values for argument 'a'

形式参数

  • 位置参数
  • 位置参数是根据实参位置依次进行参数接收的
def func(a, b):		#a, b均为位置参数
    print(a, b) 	

func(1, 2)     

#输出结果:1 2
  • 默认值参数
  • 默认值参数不需要进行对应的传参,其自身具有默认值,但是当默认值参数与对应传入的参数不同时,还是会进行参数的覆盖(默认值参数的存在可以保证程序功能的向后兼容,即添加新功能之后,不会影响老用户的正常使用)
def func(a = 1):     #a为默认值参数,默认值为1
    print(a) 

func(2)     #向默认值参数传入参数,并且传入的实参与默认值不同

#输出结果:2
  • 默认值参数不能写在位置参数之前
def func(a = 1, b):		#a为默认值参数,b为位置参数,并且位置参数在默认值参数后
    print(a, b) 

func(1, 2)

#引发异常:non-default argument follows default argument
  • 默认值参数赋值语句,是在使用 def 关键字定义函数的时候被执行的,且仅执行这一次,如果每一次都不对默认值形参传入实际参数,相同的"预计算"值将在每次调用时被使用(除非再执行一次def语句,重新进行id赋值),如果此时默认值为列表、字典或类实例等可变对象,就会产生一些奇怪的现象
i = 1

def func(a = i):     #a为默认值参数,默认值为i
    print(a) 

i = 2
func()

#输出结果:1

def func(a = []):     	#a为默认值参数,默认值为一个空列表
    a.append(1)			#每一次调用函数,都会对同一个列表对象追加1
    print(a)

func()	
func()
func()

#输出结果:
"""
[1]
[1, 1]
[1, 1, 1]
"""

def func(a = []):
    a.append(1)
    print(a)

func()
func()

def func(a = []):		#创建一个新的函数对象,再次对全局变量func赋值,重新对默认值参数赋值
    print(a)

func()

如果不想在后续调用之间共享默认值,应以如下方式编写函数

def func(a = None):     #a为默认值参数,默认值为None
    if a is None:
        a = []

    a.append(1)
    print(a)

func()
func()
func()

#输出结果:
"""
[1]
[1]
[1]
"""
  • 特殊参数
  • 默认情况下,参数可以使用位置参数或关键字关键字的形式传递给 Python 函数。但是为了让代码易读、高效,我们最好可以在函数定义的时候就限制参数的传递方式。这样,开发者只需查看函数定义,即可确定某一个参数项是仅可以使用位置参数形式、可以使用位置参数或关键字参数形式,还是仅可以使用关键字参数形式进行参数传递
def func(pos_or_kwd, *, kwd1, kwd2):
	pass	
#位于符号"*"后面的形参kwd1和kwd2仅可以使用关键字参数形式进行参数传递
#位于符号"*"前面的形参pos_or_kwd既可以使用位置参数形式,又可以使用关键字参数形式进行参数传递

def func(pos_or_kwd, *, kwd1, kwd2):
    print(pos_or_kwd, kwd1, kwd2)

func(1, kwd1 = 2, kwd2 = 3)

#输出结果:1 2 3

def func(pos_or_kwd, *, kwd1, kwd2):
    print(pos_or_kwd, kwd1, kwd2)

func(1, 2, kwd2 = 3)

#引发异常:func() takes 1 positional argument but 2 positional arguments (and 1 keyword-only argument) were given
  • 不定长参数之个数可变的位置参数
  • 使用*定义个数可变的位置参数。个数可变的位置参数可以一次性接收连续的任意多个位置参数,这些实参保存在一个元组中(定义函数时,无法确定用户会传递的位置参数个数时,可以使用)
def func(*args):
    print(args)

func(1, 2, 3)

#输出结果:(1, 2, 3)

def func(*args):
    print(args)

func()

#输出结果:()
  • 只能有一个个数可变的位置参数
def func(*args1, *args2):
    pass

#代码效果:SyntaxError: invalid syntax
  • 个数可变的位置参数可以位于位置参数的后面
def func(a, *args):
    print(a, args)

func(1, 2, 3)

#输出结果:1 (2, 3)
  • 不定长参数之个数可变的关键字参数
  • 使用**定义个数可变的关键字参数。个数可变的关键字参数可以一次性接收连续的任意多个关键字参数,这些实参保存在一个字典中(定义函数时,无法确定用户会传递的关键字参数个数时,可以使用)
def func(**kwargs):
    print(kwargs)

func(a = 1, b = 2, c = 3)	#变成字典的时候所有的字母都会转化为字符串类型对象

#输出结果:{'a': 1, 'b': 2, 'c': 3}

def func(**kwargs):
    print(kwargs)

func()

#输出结果:{}
  • 只能有一个个数可变的关键字参数
def func(**kwargs1, **kwargs2):
    pass

#代码效果:SyntaxError: invalid syntax
  • 不能有其他参数在**参数后
def func(**kwargs, *args):
    pass

#代码效果:SyntaxError: invalid syntax

def func(**kwargs, a):
    pass

#代码效果:SyntaxError: invalid syntax

def func(**kwargs, a = 10):
    pass
    
#代码效果:SyntaxError: invalid syntax
  • 在关键字参数可以非连续地接收关键字参数(本质可能是函数在调用传参的时候,是同时将所有关键字参数进行传参的,所有只有没有形参与之匹配的关键字参数才会被个数可变的关键字参数接收)
def func(a, b, **kwargs):
    print(a, b, kwargs)

func(a = 1, d = 2, b = 3, c = 4)

#输出结果:1 3 {'d': 2, 'c': 4}

lambda匿名函数

关键字lambda表示匿名函数,因为函数没有名字,所以不必担心函数名冲突。除了匿名,以及形参列表中不能对形参指定类型以及无法使用装饰器进行装饰以外,其他和普通的自定义函数没有区别
可以将匿名函数赋值给一个变量,通过调用此变量来调用lambda函数

func = lambda x : x+1	#定义一个lambda匿名函数对象,并将引用保存在变量func中

re = func(0)		#调用lambda匿名函数,并得到返回值
print(re)

对于lambda表达式

  • 前半部分(lambda [形参列表] :)即为普通函数的def可执行语句,只会执行一次,而且是在接触到lambda表达式的时候就会执行,同时会把默认值参数进行赋值,
  • 后半部分即为普通函数的函数体,只有在调用该函数的时候才会执行里面的代码
i = 1

func = lambda a = i: i

i = 2
re = func()
print(re)

#输出结果:2

func = lambda x = [] : (x.append(1), x)		#每一次调用函数都会向同一个列表中追加1

func()
func()
re = func()
print(re)

#输出结果:(None, [1, 1, 1])

func = lambda : math.radians(1)

re = func()
print(re)

#输出结果:name 'math' is not defined

func = lambda : math.radians(1)

#代码效果:毫无动静,正常结束

func = lambda : math.radians(1)

re = func()
print(re)

#输出结果:0.017453292519943295
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值