Loading

函数基础语法及使用

函数

函数是用来盛放代码的容器,一个函数就是一个功能。

作用:

1、减少代码冗余。

2、增强代码可读性。

3、使程序易于扩展。

函数使用

原则:先定义,后调用。

函数名定义规则与变量名相同,函数名尽量用动词。

# 定义函数基本结构
def 函数名():
    代码体
# 定义函数的完整结构
def 函数名(参数1,参数2,参数3。。。):
    """
    注释信息
    """
    代码体
    return 返回值

定义阶段:只检查语法,不执行代码。

和定义变量相同,先申请一块内存空间,将代码放进去,将内存空间与函数名关联。

# abc虽然没有定义变量,但这是没定义变量引发的逻辑错误,并没有语法错误,所以不会报错。
def f():
    a + b + c

调用阶段:执行函数体代码。

调用阶段是先通过函数名找到代码体对应的内存空间,括号()表示执行代码体的代码。

名字对应的是内存地址,打印函数名得到的是函数体的内存地址。

def f():
    pass
print(f)
<function f at 0x0000000000507160>

函数定义的三种形式

无参函数

def f1():
	代码体

有参函数

def f2(x,f):
    代码体

空函数

用于先定义好程序大概所要用的功能,然后再根据思路补全函数。

def f3():
    pass

函数的提示信息

Python不会限制传入参数的数据类型,但我们在定义时可以提示调用者函数参数和返回值的数据类型。

def func(x:'参数1的提示信息',y:'参数2的提示信息')->'返回值得提示信息':
    pass

# 查看提示信息,返回一个字典。
print(func.__annotations__)
{'x': '参数1的提示信息', 'y': '参数2的提示信息', 'return': '返回值的提示信息'}

定义时的注释

def f1(x,y):
    """
    函数功能注释
    :param x: 参数x的注释
    :param y: 参数y的注释
    :return: 返回值得注释
    """
    pass

# 使用help()内置函数查看函数注释信息.
help(f1)

Help on function f1 in module __main__:

f1(x, y)
    函数功能注释等
    :param x: 参数x的注释
    :param y: 参数y的注释
    :return: 返回值得注释

函数调用的三种形式

1、语句形式。

f1()

2、表达式形式。

ret = f() + 10

3、函数调用可以当做一个参数传给另一个函数。

f1(f2())

函数的返回值

return的两种作用

1、控制返回值。

情况1:没有return或return后为空。此时返回值为None。

def f1():
    pass
print(f1())
None

def f2():
    return
print(f2)
None

情况二:单个返回值。

def f1():
    return 10
print(f1())
10

情况三:用逗号分隔返回多个值。多个值会以元组的形式返回。

def f1(a,b,c):
    return a,b,c

print(f1(1,2,3))
(1, 2, 3)

返回值的id和函数调用的id是同一个。

def f(a):
    return a
res = f(3)
print(id(res), id(f(3)))
8791056713440 8791056713440

2、终止函数

函数内可以有多个return,函数执行过程中,遇到return就会立即终止函数,并将return后面的值当做本次调用的结果返回。

def f1(a,b):
    if a >b:
        return a
    else:
        return b
print(f1(10,20))
20

函数的参数

两大类:

  • 形参:在定义函数时,括号内指定的参数,称之为形式参数,简称形参,本质就是变量名。
  • 实参:在函数调用时,括号内传入的值,称之为实际参数,简称实参,本质就是变量值。
def total(a,b):
    print(a + b)
total(10,20)

# a,b就是形参。
# 10,20就是实参。

实参与形参的关系:

在调用函数时,实参的值会绑定给形参,该绑定关系可以在函数内使用。在函数调用结束后,会解除绑定关系。

def f1(a,b):
    print(a + b)  # 函数内可以访问到a和b
f1(1, 2)
3

# 函数外则无法访问到a和b
print(a, b)
NameError: name 'a' is not defined

形参:

在函数调用时调用一次函数形参只能被传一次值,不能重复传值。

位置形参:

在定义函数时,按照从左到右的顺序依次定义的变量名,称之为位置形参。

特点:必须被传值,多一个不行,少一个不行。

# 必须传入和位置参数同样数量的值。
def func(a, b):
    pass

func(10)
TypeError: func() missing 1 required positional argument: 'b'

# 不能重复传值。
func(111,222,a=333)
TypeError: func() got multiple values for argument 'a'

默认形参(具有默认值得形参):

在定义函数时,就已经为某个形参赋值,该形参就称为默认参数。

特点:在调用阶段可以不用给默认形参传值。若有传值,新值会覆盖默认值。

def f(x, y=10):
    print(x, y)
f(20)
20 10

注意:默认形参的值是在函数定义阶段规定死,之后的改变不会影响。默认形参的值最好为不可变类。

n = 10
print(f'函数定义前 n:    {id(n)}')

def f1(x, y=n):  # 在定义阶段 y所指向的内存地址已经固定。
    print(f'函数内 y:    {id(y)}')

n = 111  # n 的值为不可变类型,在值改变后内存地址也改变,但不会影响函数已定义的值。
print(f'函数定义后 n:    {id(n)}')
f1(222)

函数定义前 n:    8790942025664
函数定义后 n:    8790942028896
函数内 y:    8790942025664
222 10

注:默认形参的值若为可变类型,则会被函数定义后的改变所影响。

n = [10,20]
print(f'函数定义前 n:    {id(n)}')

def f1(x, y=n):  # 在定义阶段 y所指向的内存地址已经固定。
    print(f'函数内 y:    {id(y)}')
    print(x,y)

n.append(30)  # n 的值为可变类型,在值改变后内存地址不变,还是指向同一个内存地址。
print(f'函数定义后 n:    {id(n)}')
f1(222)

函数定义前 n:    41272960
函数定义后 n:    41272960
函数内 y:    41272960
222 [10, 20, 30]

写函数时,函数不应该被定义之后的外界因素影响。

也就是说,在函数定义完后,函数运行结果应该是可预测的。默认形参的默认值若为可变类型,会让最后的结果充满不确定性,给人带来困惑。

可变长参数*和**

*会将溢出位置实参汇总成元组,然后赋值给其后变量名,通常应该是args。

def f(a,b,*args):
    print(a,b)
    print(args)
f('哼','哧','哈','嘿')
哼 哧
('哈', '嘿')

# 也可以不给*传值。结果为空元组。
f('哼','哧')
哼 哧
()

利用*写一个接收能任意个数值的求和函数。

def total(*args):
    n = 0
    for i in args:
        n += i
    return n
print(total(1,2,3,4,5))
15

**会将溢出关键字实参汇总成字典,然后赋值给其后变量名,通常应该是kwargs。

def f(a,b,**kwargs):
    print(a,b)
    print(kwargs)

f('哼','哧',x='哈',y='嘿')

哼 哧
{'x': '哈', 'y': '嘿'}

# 同样也可以不给**传值,结果为空字典。
哼 哧
{}

*和**的组合使用,能接收符合语法的任意实参。

def f(*args,**kwargs):
    print(args)
    print(kwargs)

f(1,2,3,4,5,m=6,n=7,x=8,y=9)
(1, 2, 3, 4, 5)
{'m': 6, 'n': 7, 'x': 8, 'y': 9}

命名关键字形参

在*args之后,**kwargs之前的参数,也可以有默认值,仅接受关键字实参。

def f(a,b,*args,c='嘿',d,**kwargs):
    print(a,b)
    print(args)
    print(c,d)
    print(kwargs)

f(10,20,30,40,d='哈',k1='v1',k2='v2')

10 20
(30, 40)
嘿 哈
{'k1': 'v1', 'k2': 'v2'}

形参最终顺序

语法规定,违反则报语法错误。

func(位置形参,默认形参,*args,命名关键字形参,**kwargs)

实参:

位置实参:

在调用函数时,按照从左到右的顺序依次传入的值,称之为位置实参。

特点:按照位置与形参一一对应。

# 必须传入和位置参数同样数量的值,a和b能收到什么值取决于实参的位置。
def func(a, b):
    pass

func(10,20)
func(20,10)

关键字实参:

在调用函数时,按照key=value的形式指定的实参,称之为关键字实参。

特点:可以打乱顺序,但仍能为指定的形参传值。

def func(a, b):
    print(a,b)

func(10,20)
10 20
func(b=20,a=10)
10 20

# 指定关键字实参时,形参名不要带引号,否则会语法报错。
func('b'=20,'a'=10)
SyntaxError: expression cannot contain assignment, perhaps you meant "=="?

可变长实参*和**

在函数调用时,*会将后面的可迭代对象拆分成位置实参。拆分后的值应与位置形参的数量一致。如果这个可迭代对象为字典,传入函数的值为字典的key。

def func(a,b,c):
    print(a,b,c)
func(*[1,2,3])
1 2 3

func(*{'k1':1,'k2':2,'k3':3})
k1 k2 k3

在函数调用时,**会将后面的字典(必须是字典)拆分成关键字实参。字典的key必须是形参名。

def func(a,b,c):
    print(a,b,c)

func(**{'a':1,'c':2,'b':3})
1 3 2

实参最终顺序

func(位置实参,关键字实参,*iterable,**dict)

*和**的魔性用法

在函数嵌套中,外层函数使用*和**接收任意参数,内层函数使用*和**将接收的任意参数拆分。这个方法在装饰器中会使用到。

def wrapper(*args,**kwargs):  # wrapper先接收为(1,2,3) {'a':4,'b':5},传给inner
    def inner(*args,**kwargs):  # 最后接收到1,2,3,a=4,b=5,再通过*和**聚合为(1,2,3) {'a':4,'b':5}传给args和kwargs
        print(args,kwargs)
    inner(*args,**kwargs)  # 调用inner接收后用*和**拆分为1,2,3,a=4,b=5 传给inner函数。

wrapper(1,2,3,a=4,b=5)
(1, 2, 3) {'a': 4, 'b': 5}  # 最终输出

在变量解压赋值中,*能将溢出的值聚合成一个列表赋值给后面的变量名,该名约定俗成为下划线 _ 。

t = (1,2,3,4)
a, *_ = t
print(a, _)
1 [2, 3, 4]

*_, a = t
print(_,a)
[1, 2, 3] 4

a, *_, b = t
print(a, _, b)
1 [2, 3] 4

函数对象

函数是第一等公民

1、函数可以当变量赋值

def f1():
    print('from f1')
f2 = f1  # 将f1的内存地址关联给f2
f2()  # f2加括号也能调用
from f1

2、可以当做参数传给另一个函数。

def f1():
    print('from f1')

def f2(x):
    x()
f2(f1)  # 将f1赋值给x,在f2内调用f1()
from f1

3、可以当做一个函数的返回值。

def f1():
    print('from f1')

def f2(x):
    return x

ret = f2(f1)

ret()  # 拿到返回值f1,加括号也能调用

from f1

4、函数可以当做容器类型的元素。

def func():
    return 100

l1 = [1,2,func]

res = l1[-1]()  # 通过索引取到func,加()能调用
print(res)

100

通过以上特性,可以写一个函数功能字典。

def haha():
    print('哈哈')
    
def hehe():
    print('呵呵')

# 若要添加功能,只需添加函数和功能字典即可,主逻辑代码无需改变。
def hei():
    print('嘿嘿')

func_dict = {
    '0':['退出'],
    '1':['打印哈哈',haha],
    '2':['打印呵呵',hehe],
    # 添加一个key:value
    '3':['打印嘿嘿',hei],
}


while 1:
    for i in func_dict:
        print(i,func_dict[i][0])
    choise = input('>>>').strip()
    if choise == '0':
        break
    elif choise in func_dict:
        func_dict[choise][-1]()
    else:
        print('更多功能开发中...')

函数嵌套

嵌套定义

在函数内再定义函数。

def wrapper():
    def inner():
        print('from inner')
    return inner

# 通过调用wrapper得到返回值为inner,然后将返回值加()即可调用.
res = wrapper()
res()
from inner

使用嵌套定义,可以写一个同类型多种功能的函数,通过输入参数选择不同的功能。这样在调用时只需知道一个函数的用法即可,而无需调用多个函数。

例如,写一个计算圆的周长或面积的函数。

from math import pi

def circle(radius,mode=0):
    def perimiter(radius):
        return 2 * pi * radius
    def area(radius):
        return pi * (radius ** 2)
    # 判断如果mode为0则计算周长,如果为1则计算面积。
    if mode == 0:
        return perimiter(radius)
    elif mode == 1:
        return area(radius)
    else:
    	return 'input 0 or 1'
    
res = circle(10,mode=1)
print(res)
314.1592653589793

嵌套调用

在一个函数内再调用其他函数。

例如,写一个比较四个数返回最大数的函数,可以先写一个比较两个数的函数,然后通过调用这个函数来比较四个数。

def max2(a,b):
    return a if a > b else b

def max4(a,b,c,d):
    ret1 = max(a,b)
    ret2 = max(ret1,c)
    ret3 = max(ret2,d)
    return ret3

res = max4(1,2,3,4)
print(res)
4

使用嵌套调用,可以让一个功能复杂的函数拆分成多个功能简单的函数,然后再通过一个函数调用多个简单函数。这样能使代码逻辑更清晰。

posted @ 2020-08-02 17:11  吃了好多肉  阅读(579)  评论(0编辑  收藏  举报