跳转至

函数 def

函数概述

Python 函数主要的作用就是打包代码,可以最大程度地实现代码重用,减少冗余的代码,将不同功能的代码段进行封装、分解,从而降低结构的复杂度,提高代码的可读性。

函数的基本使用

函数的创建和调用

Python
1
2
3
4
5
6
# 创建
def fun():
  pass

# 使用
fun()

函数的参数类型

函数的参数分为: 形式参数(形参)和实际参数(实参)。

  • 形参: 函数定义时候的参数名称。
  • 实参: 函数调用时实际传入的参数。
Python
1
2
3
4
5
6
# 创建包含参数的函数,如果有多个参数括号内使用逗号隔开,传递多个参数
def fun(name):
  print(name)

# 使用
fun("hello word")  # --> hello word

函数的返回值

使用关键字 return,将函数需要返回的值进行返回,只要函数执行返回语句,就会立即返回,而不会执行之后的代码。如果函数没有返回值,那么函数默认也会返回一个 None,函数如果有多个返回值,将以元组的方式返回。

Python
def fun():
  return 11

print(fun())  # --> 11

def fun1():
  pass

a = fun1()
print(a)  #  --> None

函数的参数定义

函数的参数就是定制的接口,只要按照函数的要求,将指定的参数正确传递,函数就可以按指定的程序运算返回最终的结果,函数的参数可以分为:位置参数和关键之参数和默认参数以及收集参数。

  • 位置参数: 只需要按照正确的位置传递参数即可。
  • 关键字参数: 只需要知道关键字对关键字进行赋值传递。
  • 默认参数: 如果函数的默认参数没有传递参数就会执行默认参数进行传递。
  • 收集参数: 定义函数的时候在参数前加*表示收集参数,本质是通过元组进行打包,将传入的多个参数打包到元组里,如果定义参数的时候有收集参数和其他参数,此时传入参数的时候,其他参数都需要使用关键字参数赋值,不然都会打包到收集参数中,收集参数同样可以将传入的参数打包成字典,此时传递参数就必须使用关键字参数。
  • 解包参数: 在实参中使用星号表示解包参数,其中包含一个星号和两个星号,一个星号表示元组的形式解包,可以是元组也可以是列表,两个星号表示字典的形式关键字解包,字典的键对应函数的关键字,必须一一对应。

注意: 位置参数必须在关键字参数之前,如果函数创建使用了默认参数,需要将默认参数放到参数最后。

使用help()可以对函数进行查看,函数参数里面有 /,表示该符号左边必须使用位置参数,不可以使用关键字参数。

如果函数参数有*则表示左边既可以使用关键字参数也可以使用位置参数,右边只可以使用关键字参数。

Python
# 位置参数
def fun1(a, b):
  print(a*b)
fun1(1, 2)  # --> 2


# 关键字参数
def fun2(a, b):
  print(a*b)
fun2(a=1, b=2)  # --> 2


# 默认参数
def fun3(a, b=2):
  print(a*b)
fun2(a=1)  # --> 2


# 包含/的函数参数
def fun4(a, /, b):
  print(a*b)
fun4(1, b=4)  # --> 4


# 包含*的函数参数
def fun5(a, *, b):
  print(a*b)
fun5(1, b=4)  # --> 4


# 收集参数
def fun6(*arg):
  print(arg)
fun6(1, 2, 3)  # --> (1, 2, 3)本质是元组


# 如果存在收集参数和其他参数其他参数需要使用关键字参数,这个时候可以使用*强制后面的参数使用关键字参数
def fun7(*arg, *, a, b):
  print(arg, a, b)
fun7(1, 2, 3, a=7, b=9)  # --> (1, 2, 3) 7 9


# 收集参数,**参数打包成字典进行传递
def fun8(**arg):
  print(arg)
fun8(a=1, b=2)  # --> {"a": 1, "b":2}


# 综合起来
def fun9(a, *b, **c):
  print(a, b, c)
fun9(1, 2, 3, a = 1, b = 2)  #  --> 1 (2, 3) {"a":1,"b":2}


# 解包参数
# 一个星号
a = (1, 2, 3)
def fun10(a, b, c):
  print(a, b, c)
fun10(*a)  # --> 1 2 3


# 两个星号
a = {"a": 1,"b": 2,"c": 3}
def fun10(a, b, c):
  print(a, b, c)
fun10(*a)  # --> 1 2 3

作用域

作用域概述

一个变量可以被访问的范围,通常作用域总是由它在代码中的赋值位置决定的。

  • 局部作用域: 如果一个变量定义在一个函数内部,那个这个变量称为局部变量。
  • 全局作用域: 如果在任何函数的外部定义一个变量,那这个变量称为全局变量,如果在函数内部和函数外部存在同名变量,函数内部的变量会覆盖函数外部的变量。

global 语句

修改全局变量的值:

Python
1
2
3
4
5
x = 999
def fun():
  global x
  x = 521
  print(x)

嵌套函数

嵌套函数概述

内部函数声明完成后需要在外部函数引用,内部函数可以访问外部函数变量,无法直接进行修改外部函数的变量值,如果一定要修改需要使用 nonlocal 语句进行修改。

Python
def funa():
  x = 999
  def funb():
    x = 520
    print("funb x =", x)
  funb()
  print("funa x =", x)

# funb x = 520
# funa x = 999

nonlocal语句

修改外部函数变量值,如果在嵌套函数中,内部函数使用该语句,此时需要外部函数函数内部有该变量,不然在内部函数使用该语句会报错,因为该语句是为了内部函数使用和修改外部变量。

Python
def funa():
  x = 999
  def funb():
    nonlocal x = 520
    print("funb x =", x)
  funb()
  print("funa x =", x)

# funb x = 520
# funa x = 520

LEGB 规则,作用域规则

级别由高到低 作用域
1 Local(局部作用域)
2 Enclosed(嵌套函数的外部作用域)
3 Global(全局作用域)
4 Build-In(内置函数),不可以和 python 内置函数重名,否则变量会覆盖内置函数,内置函数作用域最低

闭包函数

内部函数做为外部函数的返回值,通过修改外部函数的变量值达到变量储存,实时修改变量, 函数的调用和创建需要使用括号其他时候都不需要使用括号,对于嵌套函数外层函数的作用域是会通过某种形式保留下来,闭包函数又叫做工厂函数,闭包函数必须由嵌套函数实现,闭包函数可以将嵌套函数的内部函数直接在程序中访问。

  • 利用嵌套函数的外层作用域具有记忆能力这个特性,让数据保留在外层函数的参数或者变量中。
  • 将内层函数做为返回值返回了,这样就可以从外部间接调用内部函数。
Python
# 例一
def funa(x=1):
    def funb():
        print(x)
    return funb


test = funa()
test()

# 例二
def a(x):
    def b(y):
        print(x ** y)
    return b


test1 = a(2)
test2 = a(3)
test1(2)  # --> 2的平方 4
test2(2)  # --> 3的平方 9

# 例三,可以进行坐标的实时更新
def a(x=0, y=0):
    def b(x1, y1):
        nonlocal x, y
        x += x1
        y += y1
        print(x, y)
    return b


test = a()
test(2, 3)
test(3, 4)

装饰器

将一个函数做为参数传递给另一个函数,可以利用装饰器语法糖更简单实现,可以发现修饰器其实是建立在闭包的基础上,增加语法糖缩写一些代码实现修饰器的效果。

Python
import time
# 不适用修饰器进行实现一个函数做为另一个函数参数
def a(fun):
    print("开始调用函数。。")
    start = time.time()
    fun()
    print("函数调用结束··")
    stop = time.time()
    print(f"函数共调用时间为", stop - start)
def b():
    print("hello word")
    time.sleep(2)
a(b)

# 使用修饰器进行实现
def time_master(fun):
    def test():
        print("开始调用函数。。")
        start = time.time()
        fun()
        print("函数调用结束··")
        stop = time.time()
        print(f"函数共调用时间为", stop - start)
    return test
@time_master
def b():
    print("hello word")
    time.sleep(2)
b()

# 实际使用闭包函数
def time_master(fun):
    def test():
        print("开始调用函数。。")
        start = time.time()
        fun()
        print("函数调用结束··")
        stop = time.time()
        print(f"函数共调用时间为", stop - start)
    return test
def b():
    print("hello word")
    time.sleep(2)
a = time_master(b)
a()

# 同样可以进行多次调用,此时需要对应多个语法糖,每个执行函数对应一个语法糖
def time_master(fun):
    def test():
        print("开始调用函数。。")
        start = time.time()
        fun()
        print("函数调用结束··")
        stop = time.time()
        print(f"函数共调用时间为", stop - start)

    return test
@time_master
def b():
    print("hello word")
    time.sleep(2)
@time_master
def c():
    print("hello")
    time.sleep(4)
b()
c()

# 同样可以多个装饰器调用一个函数
def add(func):
    def inner():
        x = func()
        print(1)
        return x + 1
    return inner
def cube(func):
    def inner():
        x = func()
        print(2)
        return x * x * x
    return inner
def square(func):
    def inner():
        x = func()
        print(3)
        return x * x
    return inner
@ add
@ cube
@ square
def test():
    return 2
print(test())
# 结果为65,此时程序先执行最下面的语法糖,然后继续向上

# 装饰器传入参数
def time_master(arg):
    def test1(fun):
        def test2():
            print("开始调用函数。。")
            start = time.time()
            fun()
            print("函数调用结束··")
            stop = time.time()
            print(f"{arg}函数共调用时间为", stop - start)
        return test2
    return test1
@time_master(arg="b")
def b():
    print("hello word")
    time.sleep(2)
@time_master(arg="c")
def c():
    print("hello")
    time.sleep(4)
b()
c()
# 不使用装饰器拆解开来
def time_master(arg):
    def test1(fun):
        def test2():
            print("开始调用函数。。")
            start = time.time()
            fun()
            print("函数调用结束··")
            stop = time.time()
            print(f"{arg}函数共调用时间为", stop - start)
        return test2
    return test1
def b():
    print("hello word")
    time.sleep(2)
def c():
    print("hello")
    time.sleep(4)
b = time_master(arg="b")(b)
c = time_master(arg="c")(c)
b()
c()
Python
# 题目要求:
# 请在此处补充装饰器 type_check() 的代码
def type_check(aa):
    def test(fun):
        def test2(arg):
            if type(arg) == aa:
                return fun(arg)
            else:
                return "参数错误"
        return test2
    return test

print("<<<--- 测试整数 --->>>")

@type_check(int)
def double(x):
    return x * 2

print(double(2))      # 这里打印结果应该是 4
print(double("2"))    # 这里打印结果应该是 “参数类型错误”

print("\n<<<--- 测试字符串 --->>>")

@type_check(str)
def upper(s):
    return s.upper()

print(upper('I love FishC.'))   # 这里打印结果应该是 I LOVE FISHC
print(upper(250))               # 这里打印结果应该是 “参数类型错误”

lambda 表达式

lambda 表达式又称为匿名函数,建议处理简单的函数模型,因为该表达式是一行代码,lambda 可以直接放在列表里,同样也直接可以当成函数写入需要传递函数的参数里。

语法: lambda arg1, arg2, ... argN : expression,其中 arg 为函数的参数,expression 为代码块和返回值

传统语法:

Python
def <lambda>(arg1, arg2, ... argN):
        return expression
Python
# 例一
# 普通函数创建
def fun(x):
  return x * x
# 使用lambda表达式创建
test = lambda x : x * x
# 调用
fun(3)
test(3)

# 例二
# 将lambda表达式放在列表中使用
a = [lambda x : x * x, 1, 2]
a[0](a[1])  # --> 1
a[0](a[2])  # --> 4

# 例三
# 将lambda表达式当作函数参数赋值
test = map(lambda x : ord(x) + 10, "aaa")
print(list(test))  # --> [107, 107, 107]
Python
# 装饰器不使用lambda表达式
def add(func):
    def inner():
        x = func()
        return x + 1
    return inner

def cube(func):
    def inner():
        x = func()
        return x * x * x
    return inner

def square(func):
    def inner():
        x = func()
        return x * x
    return inner

@add
@cube
@square
def test():
    return 2

print(test())

# 使用lambda表达式
@lambda func: lambda x=func(): x + 1
@lambda func: lambda x=func(): x * x * x
@lambda func: lambda x=func(): x * x
def test():
    return 2
print(test())
Python
# 不使用lambda表达式
power = {"吕布":999, "关羽":888, "刘备":666, "张飞":900, "赵云":789, "不二如是":999}

# 请 lambda 表达式和 filter() 函数配合,替换下面的代码
greater = []
for k, v in power.items():
    if v > 800:
        greater.append((k,v))
# 请 lambda 表达式和 filter() 函数配合,替换下面的代码

print(greater)
[('吕布', 999), ('关羽', 888), ('张飞', 900), ('不二如是', 999)]

# lambda表达式
power = {"吕布":999, "关羽":888, "刘备":666, "张飞":900, "赵云":789, "不二如是":999}
greater = list(filter((lambda x: x[1] > 800), power.items()))
print(greater)
Python
>>> members = {
    "鱼C工作室" : {"小甲鱼":83, "不二如是":89, "二师兄":64, "小师妹":75, "鱼小二":96},
    "复仇者联盟" : {"钢铁侠":85, "绿巨人":39, "黑寡妇":82, "鹰眼":73, "雷神":99},
    "奥特曼家族" : {"迪迦":99, "艾斯":84, "泰罗":63, "佐菲":78, "赛文":78}}
>>>
>>> # 请在此处添加一行代码,完成题目要求,并将结果保存在变量 x 中
>>>
>>> print(x)
['鱼C工作室:二师兄', '复仇者联盟:绿巨人', '奥特曼家族:泰罗']


# 如下
t = [':'.join((i, min(members[i].items(), key=lambda x: x[1])[0])) for i in members]
# 分析
# 采用了列表推导式的方法,先进性字典键的循环,从而得到对应的值,然后进行返回字典对象
# 利用min函数求出字典中最小值,然后使用lambda表达式去取对应的键值
# 使用join函数将最开始的i(键)和最小的键值进行组合最终得到新的列表

生成器

生成器是含有 yield 语句(或 yield 表达式)的函数所返回的对象。也就是说,要创建一个生成器,我们首先要定义一个函数,该函数内将使用 yield 表达式或 yield 语句来表示每次生成的值。该函数的返回值是一个生成器对象,通过把这个函数的返回值赋给一个变量,我们就得到了一个生成器对象变量,对这个对象进行 send 和 next 等操作,即可实现生成器的功能。

定义了一个生成器,只有在每次调用的时候才会去执行输出对应生成器的值,这个就跟 return 语句不一样了,return 语句一执行,就不会管后面的语句了,yield 语句是 “打断并返回,然后下次执行就从下一条语句继续”。

Python
# 定义一个简单的生成器
def test():
    i = 0
    while i <= 5:
        yield i
        i += 1
test1 = test()
for x in test1:
    print(x)

# 可以使用next()函数进行输出生成器值
test2 = test()
next(test2)
next(test2)
next(test2)
# 全部输出在进行输出会报错,生成器不可以使用下标索引

# 使用生成器实现斐波那契数列
def aa():
    x = 0
    y = 1
    while True:
        yield x
        x, y = y, x + y
test = aa()
for i in test:
    print(i)
# 如果不设置循环结束语句会一直进行输出

# 生成器的调用,如下,111会被输出,for循环每调用一次生成器就会输出一个对应的i,当while循环结束对应输出了print111
def aa():
    i = 0
    while i <= 5:
        yield i
        i += 1
    print(111)
test = aa()
for i in test:
    print(i)

生成器表达式

生成器表达式是每次访问产生一个结果;列表推导式是将所有的结果创建为一个列表。

语法: (expression for target in iterable)(expression for target in iterable if condition)

Python
1
2
3
a = (i for i in range(10))
for x in a:
  print(x)

递归函数

函数调用自身的行为称为递归,所以要想正确调用递归,函数执行需要有一个结束条件,并且每次调用都要向这个结束条件进行推进。

注意递归的算法,会将所有函数的最后一个返回值计算完,然后再一层一层向上计算。

Python
# 例一
def test(n):
    if n > 0:
        print(n)
        n -= 1
        test(n)
test(5)

# 例二,求一个数的阶乘 --> 3 3*2*1
# 迭代
def test(n):
    x = n
    for i in range(1, n):
        x *= i
    return x
print(test(12))
# 递归
def test1(n):
    if n == 1:
        return n
    else:
        return n * test1(n-1)
print(test1(12))

# 例三,斐波那契数列
# 迭代
def test(n):
    a = 1
    b = 1
    while n > 1:
        a, b = b, a+b
        n -= 1
    return a
print(test(5))
# 递归
def test1(n):
    if n == 1 or n ==2:
        return 1
    else:
        return test(n-1) + test(n-2)
print(test1(12))

尾递归

执行效率要比普通递归高。

Python
# 计算数的阶和 5 --> 1+2+3+4+5
# 普通递归
def test(n):
    if n == 0:
        return n
    else:
        return n + test(n - 1)
print(test(3))

# 尾递归
def test1(n, num=0):
    if n == 0:
        return num
    else:
        return test1(n - 1, num + n)
print(test1(3))

函数注释定义

函数文档

使用 help 可以查看函数文档,进而快速的了解函数的功能。必须要写在函数的开头,否则不起作用。

Python
1
2
3
4
5
6
7
def test():
  """
  解释内容
  """
  return

# 使用help(test)进行查看

类型注释

Python
def test(a:str, b:int) -> str:
  return a*b

# 注意这里仅仅是注释如果传入的参数并不是期望传递的参数python不会报错阻止

# 使用默认参数
def test1(a:str = "a", b:int = 2) -> str:
  return a*b

# 期望参数是整数列表
def test1(a:list[int], b:int = 2) -> list:
  return a*b

# 期望参数是字典类型,括号第一个为键第二个为键值
def test1(a:dict[str, int], b:int = 2) -> list:
  return list(a.keys())*b

内省

内省是程序运行前对自身进行检测的一种机制,Python 是通过特殊的属性来实现内省的。

Python
def test():
  pass

# 获取自身函数名
test.__name__

# 返回函数类型注释,返回的结果为字典,{'a': 'x', 'b': 11, 'c': <class 'list'>, 'return': 9}
test.__annotations__

# 查看函数文档,可以使用print进行格式化输出
test.__doc__

高阶函数

高阶函数定义

当一个函数做为另一个函数的参数进行传递,这个时候另外的这个函数就称为高阶函数。装饰器就是一个高阶函数,其中 map、max、min 都是高阶函数。

可以使用 functools 实现高阶函数功能。

Python
import functools

def test(x, y):
  return x+y

functools.reduce(test, [1, 2, 3, 4, 5])  # --> 15
# 等价于
test(test(test(test(1, 2), 3), 4), 5)

# 使用lambda表达式通过该模块计算10的阶乘
functools.reduce(lambda x, y: x*y, range(1, 11))  # --> 3628800

偏函数

偏函数是对函数进行二次包装,通常是将现有的函数部分参数预选给绑定,从而得到一个新的函数,该函数就是偏函数。其实就是将函数的多个参数拆分多次进行传递。同样适用模块 functools。

Python
1
2
3
4
5
6
7
8
import functools
square1 = functools.partial(pow, exp=2)
square1(2)  # --> 4
square1(3)  # --> 9

square2 = functools.partial(pow, exp=3)
square2(2)  # --> 8
square2(3)  # --> 27

@wraps装饰器

Python
def time_master(fun):
    def test():
        print("开始调用函数。。")
        start = time.time()
        fun()
        print("函数调用结束··")
        stop = time.time()
        print(f"函数共调用时间为", stop - start)
    return test
@time_master
def b():
    print("hello word")
    time.sleep(2)
b()

# 此时使用内省获取函数名为 b.__name__ --> test
# 此时并不是函数b
# 使用修饰器@wraps来修饰修饰器可以解决这一问题
import functools
def time_master(fun):
    @functools.wraps(fun)
    def test():
        print("开始调用函数。。")
        start = time.time()
        fun()
        print("函数调用结束··")
        stop = time.time()
        print(f"函数共调用时间为", stop - start)
    return test
@time_master
def b():
    print("hello word")
    time.sleep(2)
b()
# 此时再去执行发现 b.__name__ --> b