目录

  • 一、函数介绍
  • 1.1 函数的定义与调用说明
  • 二、函数的定义
  • 2.1 函数定义说明
  • 2.2 定义函数的三种形式
  • 三、函数的调用
  • 3.1 函数调用说明
  • 3.2 调用函数的三种形式
  • 四、函数返回值
  • 4.1 函数返回值说明
  • 4.2 函数返回值的三种形式
  • 4.3 return两点注意事项
  • 五、函数的参数
  • 5.1 形参和实参
  • 5.2 有参函数的五种传参方式
  • 5.2.1 位置参数
  • 5.2.2 关键字参数
  • 5.2.3 默认参数
  • 5.2.4 命名关键字的函数参数定义形式
  • 5.2.5 可变长参数
  • 5.2.6 命名关键字参数
  • 六、总结


一、函数介绍

1.1 函数的定义与调用说明

函数名本质和变量类似(打印一个变量直接看到的结果是变量的值,这是因为在内部做了转化,为了能够看得更直观,打印函数直接看到的结果是一个内存地址,从底层上讲,变量名和函数名其实都是与内存地址对应的,因为定义的过程就是在开辟内存空间),所以函数名定义规则与定义变量名一致。函数就像是一个功能,这个功能就是要执行一个动作,所以约定俗成写成动词或者动词词组。

  • 具备某一个功能的工具就是程序中的函数;
  • 实现准备工具的过程就是函数的定义;
  • 把准备好的工具拿来用即为函数的调用;

所以函数的使用必须遵循:先定义,再调用。

二、函数的定义

2.1 函数定义说明

def 函数名(参数1,参数2,...):
   """
   文档注释
   """
   代码块1
   代码块2
   代码块3
   return 返回值

# def:定义函数的关键字;
# 函数名:是用来调用函数的。函数名的命名必须能反映出函数的功能;
# 文档注释:描述该函数,来增强函数的可读性;
# 代码块:函数的功能实现代码;
# return:函数的返回值;

有了函数之后,如需实现以下打印功能,可以使用函数来完成:

# ##############################
# hello Albert
# ##############################

# 1. 先定义
def print_sym(sym, count):
    print(sym * count)


def print_msg(msg):
    print('\033[045m%s\033[0m' % msg)  # \033[045m %s \033[0m 这部分代码的作用是改变输出的颜色

# 2. 再调用(函数名加括号就是在调用函数)
print(print_sym)  # 函数名对应一个内存地址
print_sym('#', 30)
print_msg('hello Albert')
print_sym('#', 30)

2.2 定义函数的三种形式

  1. 有参函数:当函数体的功能依赖于传入的参数时,我们就使用有参函数。
def max2(x ,y):
    
    if x > y:
        print(x)
    else:
        print(y)

max2(100, 101)
  1. 无参函数:当函数体的功能不使用传入的参数时,我们传入参数显然是没有必要的;
def func():

    print('---------------------------')
    print('--------soft run-----------')
    print('---------------------------')

func()
  1. 空函数:函数体为pass,事先定义功能组织结构,通过调用函数执行某个功能;
def autn(username, password):
    """
    这是一个用户认证功能
    :param username:
    :param password:
    :return:
    """
    pass

def put():
    """
    上传功能
    :return:
    """
    pass

三、函数的调用

3.1 函数调用说明

  • 函数的使用必须遵循:先定义,后调用的原则;
  • 注意:如果没有事先定义函数而直接调用,就相当于在引用一个不存在的变量名;
  • 定义阶段:在定义阶段只检测语法,不执行函数体代码;
  • 调用阶段:根据函数名找到函数的内存地址,然后执行函数体代码;
  • 函数名加括号即调用函数;

3.2 调用函数的三种形式

  1. 基本的调用;
def func():
    print('from func')
  1. 调用并把返回结果赋值给变量;
def max2(x, y):
    if x > y:
        return x
    else:
        return y

res = max2(10, 3)
  1. 把返回结果再当做参数传入;
res = max2(max2(10, 3), 11)

四、函数返回值

4.1 函数返回值说明

什么时候应该有返回值?
函数体代码运行完毕后需要有一个返回结果给调用者;

4.2 函数返回值的三种形式

  1. 没有return,或者return后面什么都不加,或者return后面加None;
def func():
    pass

def func1():
    return

def func2():
    return None
  1. return后面跟一个常数值,返回该值本身;
def func3():
    return 1
  1. return可以使用逗号分割,返回多个值,会返回一个元组给调用者;
def fun4():
    return 1, 2, [1, 2, 3]

res4 = func4()
print(res4)  # 输出为(1, 2, [1, 2, 3])

4.3 return两点注意事项

  1. return返回值没有类型限制;
  2. return是函数结束的标志,函数内可以写多个return,但执行一次,函数就立刻结束,并把return后的值作为本次调用的返回值;

五、函数的参数

5.1 形参和实参

  • 形参(形式参数):指的是在定义函数时,括号内定义的参数,形参其实就是变量名;
  • 实参(实际参数):指的是在调用函数时,括号内传入的值,实参其实就是变量的值;

注意:
实参值(变量的值)与形参(变量名)的绑定关系只在函数调用时才会生效/绑定,在函数调用结束后立刻解出绑定;

5.2 有参函数的五种传参方式

5.2.1 位置参数

位置参数是最基本的传参方式。位置即顺序,位置参数指的就是按照从左到右的顺序依次定义的参数;

# 在定义函数时,按照位置定义的形参,称为位置形参
def foo(x, y, z):
    print(x, y, z)

"""
注意:
位置形参的特性是:在调用函数时必须为其传值,多一个不行,少一个也不行
"""

# 在调用函数时,按照位置定义的实参,称为位置实参

# foo(1, 2)  # 报错
foo(1, 3, 2)  # x=1, y=3, z=2

5.2.2 关键字参数

在调用函数时,按照key=value的形式定义的实参,称为关键字参数。关键字参数是指在位置形参的前提下,以关键字的形式为形参传值,所以它与位置参数的区别主要体现在实参的传值上面;

def foo(x, y, z):
    print(x, y, z)

foo(x=1, y=3, z=2)

"""
注意:
1、相当于直呼其名地位形参传值,意味着即便不按照顺序定义,仍能为指定的参数传值
foo(y=2, x=1, z=3)  # x=1, y=2, z=3

2、在调用函数时,位置实参与关键字实参可以混合使用,但必须遵循形参的规则
foo(1, 2, z=3) 

3、不能为同一个形参重复传值
foo(1, x=1, y=3, z=2)  # 报错

4、位置实参必须放到关键字实参的前面
foo(y=3, z=2, 1)  # 报错
"""

5.2.3 默认参数

默认参数指的是在定义阶段已经为某个形参赋值,那么该形参就称为默认参数;

# 1、定义阶段已经有值,意味着调用阶段可以不传播
def register(name, age, sex='male')
    print(name, age, sex)

register('Albert', 18, )
register('林志玲', 20, ‘female’)

# 2、位置形参必须在默认参数的前面
def func(y=1, x):  # 报错
    pass

# 3、默认形参的值只在定义阶段赋值一次,也就是说默认参数的值在定义阶段就固定死了
m = 10
def foo(x, y=m)
    print(x, y)
m = 'a'  # foo内的默认参数不会发生改变

# 4、默认参数的值应该设置为不可变类型

# 假如默认参数不是不可变类型,我们以列表为例
def register(name, hobby, l=[]):
    l.append(hobby)
    print(name, l)

register('kobe', 'play')  # kobe ['play'] 一切正常
register('james', 'read')  # james ['play', 'read']
register('Albert', 'music')  # Albert ['play', 'read', 'music'] 这就是未设置为不可变类型出现的BUG

# 数据出错的原因就是每次调用都会在同一个列表上作修改


# 为了实现同样的功能,修正后如下
def register(name, hobby, l=None):
    if l is None:
        l = []
    l.append(hobby)
    print(name, l)

register('kobe', 'play')  # 每一次调用的时候,l的默认参数都是None,因此每次都会新建一个[]
register('james', 'read')
register('Albert', 'music')

# 应用场景
# 对于经常需要变化的值,需要将对应的形参定义成位置形参
# 对于大多数情况值都一样的情况,需要将对应的形参定义成默认参数

5.2.4 命名关键字的函数参数定义形式

python提功了一种命名关键字的函数参数定义形式,利用“*”定义参数名称,“*”后的参数在函数调用时必须明确写上参数名称。

def print_info(name,age,*,job,home):
    print("{},{},{},{}".format(name,age,job,home))
    
print_info("Jack",12,job="Student",home="China")

上述代码中在“”*前面的两个参数name和age在函数调用时不需要编写参数名称,而在“*”后面定义的两个参数在函数调用时必须编写参数名称。

5.2.5 可变长参数

<1> 可变长参数基本使用

可变长度指的是参数的个数可以不固定,实参有按位置定义的实参和按关键字定义的实参,所以可变长的实参指的就是按照这两种形式定义的实参个数可以不固定。然而实参终究是要给形参传值的,所以形参必须有两种对应的解决方案来分别处理以上两种可变长度的实参。

注意可变参数的数据类型为元组,可以用for执行循环处理。

# *会将溢出的位置实参全部接受,然后保存成元组的形式赋值给一个变量args(可以任意命名,约定俗成是args)
def foo(x, y, z, *args):  # args = (4, 5, 6, 7, 8)
    print(x, y, z)  # 输出1,2,3
    print(args)  # 输出(4, 5, 6, 7, 8)

foo(1, 2, 3, 4, 5, 6, 7, 8)

# **会将溢出的关键字实参全部接收,然后保存成字典的形式赋值给kwargs
def foo(x, y, z, **kwargs):  # kwargs = {'c':3, 'a':1, 'b':2}
    print(a, y, z)
    print(kwargs)

foo(x=1, y=2, z=3, a=1, b=2, c=3)

<2> 星与星星(打散)

很多时候[*]的作用就是打散

# 一旦遇到实参加*,就把该实参的值打散
def foo(x, y, z, *args):
    print(x, y, z)
    print(args)

foo(1, 2, 3, *[4, 5, 6, 7, 8])  # foo(1, 2, 3, 4, 5, 6, 7, 8)
foo(1, 2, 3, *(4, 5, 6, 7, 8))  # foo(1, 2, 3, 4, 5, 6, 7, 8)
foo(1, 2, 3, *'hello')  # foo(1, 2, 3, 'h', 'e', 'l', 'l', 'o')

def foo(x, y, z):
    print(x, y, z)

#  foo(*[1, 2, 3, 4])  # foo(1, 2, 3, 4)  报错
foo(*[1, 2, 3])  # foo(1, 2, 3) 


# 一旦碰到实参加**,就把该实参的值打散
def bar(x, y, z, **kwargs):
    print(x, y, z)
    print(kwargs)

bar(1, 2, 3, **{'a': 1, 'b': 2})  # foo(1, 2, 3, b=2, a=1)

def boo(x, y, z):
    print(x, y, z)

# boo(1, **{'z':3, 'y':2, 'x':111})  # 报错 boo(1, z=3, y=2, x=111)
boo(1, **{'z':3, 'y':2})  # foo(1, z=3, y=2)

# *的应用场景
def sum2(*args):
    res = 0
    for num in args:
        res += num
    return res

print(sum2(1, 2, 3, 4, 5, 6, 7, 8))

# **的应用场景
def auth(name, pwd, **kwargs):
    print(name)
    print(pwd)
    print(kwargs)

auth(name='Albert', pwd='123', group='group1')

<3>组合使用(重点)

def index(name, age, gender)
    print('welcome %s %s %s' % (name, age, gender))

def wrapper(*args, **kwargs):
    """
    执行过程:
    wrapper的所有参数都原封不动地传给index,而index函数只接收三个位置参数
    星与星星的组合使用在源码中非常常见,这也是装饰器的核心之一,这非常重要
    """
    index(*args, **kwargs)

wrapper(name='Albert', age=18, gender='male')
wrapper('Albert', age=18, gender='male')
wrapper('Albert', 18, gender='male')
wrapper('Albert', 18, 'male')

5.2.6 命名关键字参数

<1>命名关键字参数导入

在[*]后面参数都是命名关键字参数,它的特点是必须被传值,约束函数的调用者必须按照key=value的形式传播,约束函数的调用者必须用指定的key名。

也就是说,**kwargs的数据形式为字典。

def auth(*args, **kwargs):
    """
    使用方式auth(name='Albert',pwd='123')
    """
    if len(args) != 0:
        print('必须用关键字的形式传参')
        return
    if 'name' not in kwargs:
        print('必须用指定的key名name')
        return
    if 'pwd' not in kwargs:
        print('必须用指定的key名pwd')

    name = kwargs['name']
    pwd = kwargs['pwd']
    print(name, pwd)

print(help(auth))  # 打印文档注释

auth(x='Albert', y='123')
auth('Albert', '123')
auth('Albert', pwd='123')
auth(name='Albert', pwd='123')

<2>命名关键字参数使用

# 使用命名关键字参数
def foo(x, y, *, z):
    print(x, y, z)

# foo(1, 2)
# foo(1, 2, a=3)  # 报错
foo(1, 2, z=3)

# 其实命名关键字参数的核心是*,args只是一个变量,有或者没有并不影响
def auth(*args, name, pwd)
    print(name, pwd)

auth(pwd='123', name='Albert')

# 使用命名关键字参数之后,可以接受参数的最复杂的情况
def foo(x, y=1, *args, z, m=2, **kwargs):  # m=2是关键字参数的默认值, **kwargs的作用是保存溢出的关键字参数

一般情况下,foo1和foo2这两种就够用了
def foo1(x, y=1):
    pass

def foo2(x, *args, **kwargs):
    pass

六、总结

  1. 知道什么是空函数,空函数的作用;
  2. 知道怎么输出一个函数所在的内存地址,print(fun());
  3. 知道怎么输出一个函数的文档注释,print(help(func()));
  4. 知道函数返回值的三种形式,(1)没有return或者return(None);(2)返回一个常数值;(3)返回用逗号分隔的多个值,会返回一个元组;
  5. 知道函数参数的可变长参数,*args和**kwargs的作用;