10. 函数

一、什么是函数

  函数也是对象,对象是内存中专门用来存储数据的一块区域。函数可以用来保存一些可执行代码的,并且可以在需要时,对这些语句进行多次调用。

二、创建函数

  创建函数也称为定义函数。我们可以使用 def 关键字来定义函数,它的语法格式如下:

def 函数名(参数列表):
    # 函数体
    pass
  • 函数名:按照命名规则起个名字,在 Python 中建议小写加下划线的方式;
  • 函数体:整个函数真正要执行的代码,一般函数体前面缩进缩进四个空格(缩进统一即可);
  • 参数列表:专门用来存储调用时传递给函数的数据;
def say_hello():
    print("This is a function!")
    print("hello world!")

定义好的函数并不会执行,你需要调用它,它才能执行里面的代码;

三、函数的调用

  函数的调用也就是执行函数。调用函数的语法格式如下:

函数名(参数列表)
say_hello()

同一个函数只需要定义一次,但是可以调用多次;

四、函数的参数

4.1、形参与实参

  在定义函数时,可以在函数名后的 () 中定义数量不等的 形参,多个使用之间使用逗号 , 隔开。形参也称 形式参数,定义形参就相当于在函数内部声明了变量,但是并不赋值。如果函数定义时,指定了形参,那么在调用函数传入的值称为 实际参数,简称 形参

  在调用阶段,实参会绑定给对应的形参,简单来说,有几个形参就要传几个实参。这种绑定关系只能在函数体内使用。实参和形参的绑定关系在函数调用时生效,函数调用结束后解除绑定关系。

# 定义函数时,指定形参
def sum(a, b):
    print(a, ' + ', b, ' = ', (a + b))

# 调用函数时,传递实参
sum(10,20)

  如果实参的个数与形参的个数不匹配会报以下的错误:

TypeError: sum() takes 2 positional arguments but 3 were given  
TypeError: sum() missing 1 required positional argument: 'b' 

4.2、参数传递的方式

  实参的传递方式有 2 种,一种是 位置参数,另一种是 关键字参数

  位置参数 就是对应位置的实参赋值给对应位置的形参。即第一个实参赋值给第一个形参,第二个实参赋值给第二个实参,以此类推。

def showInfo(name, age, gender):
    print("name: ", name, ", age: ", age, ", gender: ", gender)

showInfo("Sakura", 10, '女')

  关键字参数 可以不按照形参定义的顺序去传递,而直接根据参数名去传递参数。

def showInfo(name, age, gender):
    print("name: ", name, ", age: ", age, ", gender: ", gender)

showInfo(name = "Sakura", gender = '女', age = 10)

  按照关键字传值的时候,如果形参名不存在,会报以下的错误:

TypeError: showInfo() got an unexpected keyword argument 'sex'

  位置参数可以和关键字参数混合使用。

def showInfo(name = "unknown", age = 0, gender = "secrecy"):
    print("name: ", name, ", age: ",age, ", gender: ", gender)

showInfo("Sakura",gender = '女', age = 10)

  混合使用位置参数和关键字参数时,必须将位置参数写在前面。否则会报以下错误:

SyntaxError: positional argument follows keyword argument

  在函数调用时,我们不能通过位置参数和关键字参数同时给一个形参赋多次值,否则会报以下错误:

TypeError: showInfo() got multiple values for argument 'name'

4.3、默认参数

  定义形参时,我们可以为形参指定默认值。指定默认值 后,如果用户传递参数,则默认值没有任何作用;如果用户没有传递参数,则默认值就会生效。

def showInfo(name = "unknown", age = 0):
    print("name: ", name, ", age: ",age)

showInfo("Sakura")

  位置形参必须在默认形参的左边,否则会报以下错误:

SyntaxError: non-default argument follows default argument

  虽然形参的默认值可以指定为任意的数据类型,但是不推荐使用 可变类型

names = ["Sakura","Mikoto"]

def printName(name=names):
    for name in names:
        print(name,end='\t')
    print()

printName()

names.append("Shana")

printName()

4.4、不定长参数

  不定长参数(可变参数)指的是在调用函数时,传入的值(实参)的个数不确定。在定义函数时,我们可以在形参前面加上一个 * ,这样这个形参将会获得所有的实参。它会将所有的实参保存到一个元组中。

# *nums会接受所有的位置参数,并且会将这些参数统一保存到一个元组中
def sum(*nums):
    print("nums = ", nums, "type: ", type(nums))

    result = 0

    for n in nums:
        result += n

    print(result,'\n')

sum()
sum(1,2)
sum(1,2,3)

  在 * 号的形参只能有一个,带 * 号的参数也可以和其它参数配合使用。

# 第一个参数给num1,第二个参数给num2,剩下的都保存到nums的元组中
def sum(num1, num2, *nums):
    print("num1: ", num1)
    print("num2: ", num2)
    print("nums: ", nums, "type: ", type(nums))

    result = 0

    for n in nums:
        result += n

    print(result, '\n')

sum(1,2)
sum(1,2,3)
sum(1,2,3,4)
sum(1,2,3,4,5)

  可变参数可以不写在最后,但是,带 * 后的所有参数,必须以关键字参数的形式传递。

# 第一个位置参数给num1,剩下的位置参数给nums的元组,num2必须使用关键字参数
def sum(num1, *nums, num2):
    print("num1: ", num1)
    print("num2: ", num2)
    print("nums: ", nums, "type: ", type(nums),'\n')

sum(1,2,num2 = 3)
sum(1,2,3,num2 = 4)
sum(1,2,3,4,num2 = 5)
# 所有的位置参数后给nums,num1和num2必须使用关键字参数
def sum(*nums, num1, num2):
    print("num1: ", num1)
    print("num2: ", num2)
    print("nums: ", nums, "type: ", type(nums),'\n')
  
sum(1,num1 =2,num2 =3)
sum(1,2,num2 = 3,num1 = 4)
sum(1,2,3,num1 = 4,num2 = 5)

  如果在形参的开头直接写一个 *,则要求我们的所有参数必须以关键字参数的形式传递。

def sum(*, num1=10, num2=20, num3):
    print("num1: ", num1)
    print("num2: ", num2)
    print("num3: ", num3)
    print()

sum(num3=30)
sum(num1=11, num2=22,num3=33)
sum(num2=10,num3=10)

  * 形参只能接受位置参数,而不能接受关键字参数。但我们可以使用 ** 形参接受其它的关键字参数,它会将这些参数统一保存在字典中,字典的 key 就是参数的名字,字典的 value 就是参数的值。

def sum(**nums):
    print("nums: ", nums, ", type: ", type(nums))

sum(num1 =1,num2 = 2, num3 = 3)

  ** 形参只能有一个,并且必须写在所有参数的最后。

def sum(num1, num2, **nums):
    print("num1: ", num1)
    print("num2: ", num2)
    print("nums: ", nums, ", type: ", type(nums))

sum(num1 =1,num2 = 2, num3 = 3, num4 = 4)

4.5、参数的解包

  传递实参时,也可以在序列类型的参数前添加 * 号,这样它会自动将序列中的元素一次作为参数传递。这里要求序列中元素的个数必须和形参中的个数一致。

def fun(num1, num2, num3):
    print("num1: ", num1)
    print("num2: ", num2)
    print("num3: ", num3)

t = (10,20,30)
fun(*t)

print()

l = [10,20,30]
fun(*l)

  我们还可以通过 ** 对字典进行解包。字典的 key 要与函数的形参对应。

def fun(num1, num2, num3):
    print("num1: ", num1)
    print("num2: ", num2)
    print("num3: ", num3)

d = {"num1":10, "num2":20, "num3":30}
fun(**d)

4.6、实参的类型

  函数在调用时,解析器不会检查实参的类型,实参可以是任意类型的对象。

def showInfo(arg):
    print("arg: ", arg)

showInfo("Sakura")
showInfo(True)
showInfo(123)
showInfo(None)
showInfo([1,2,3])

  在函数中对形参进行重新赋值,不会影响到其它的变量。

def fun(arg):
    arg = 30
    print("arg: ", arg, "id: ",id(arg))

c = 10
fun(c)
print('c: ', c, "id: ", id(c))

  但是,如果形参指向的是一个对象,当我们通过形参去修改对象时,会影响到所有指向该对象的变量。

def fun(arg):
    arg[0] = 30
    print('arg: ', arg, 'id: ',id(arg))

c = [1,2,3]
fun(c)
print('c: ', c, 'id: ', id(c))

  此时,我们可以传递实参的一个副本。

def fun(arg):
    arg[0] = 30
    print("arg: ", arg, "id: ",id(arg))

c = [1,2,3]
# fun(c.copy())
fun(c[:])
print('c: ', c, "id: ", id(c))

函数的参数默认传递的是 形参的引用

五、函数的返回值

  返回值 就是函数执行以后返回的结果。我们可以通过 return 来指定函数的返回值。我们可以直接使用函数的返回值,也可以通过一个变量来接受函数的返回值。return 后面跟什么值,函数就会返回什么值。return 后面可以跟任意对象,返回值甚至可以是一个函数。

def sum(*nums):
    print("nums = ", nums, "type: ", type(nums))

    result = 0

    for n in nums:
        result += n

    print(result,'\n')

sum()
sum(1,2)
sum(1,2,3)

  如果仅仅写一个 return 或不写 return,则相当于 return None。

def fun():
    pass

print(fun())
def fun():
    return

print(fun())

  在函数中,return 后的代码都不会执行。return 一旦执行,函数自动结束。

def fun():
    print("hello")
    return
    print("world")

print(fun())

  我们还可以使用都好分割开多个返回值,此时会被 return 返回为元组的形式。

def add(a,b):
    return a,b,a+b

result = add(10,20)
print(result)
print(type(result))

函数名 是函数对象,打印函数名实际是在打印函数对象;

函数名() 是在调用函数,打印函数名()实际上是在打印函数的返回值;

return 是函数结束的标志,即函数体代码一旦运行到 return 会立刻终止函数的运行,并且会将 return 后的指当作本次运行的结果返回。

六、文档字符串

  我们可以通过 help() 函数查询 python 中函数的用法。help() 函数是 python 内置的一个函数,它的使用语法格式如下:help(函数对象)。

help(print)

  在定义函数时,我们可以在函数内部编写文档字符串,文档字符串 就是函数的说明。当我们编写了文档字符串时,就可以通过 help() 函数来查看函数的说明。文档字符串其实就是直接在函数的第一行写一个字符串,一般我们会使用三重引号,这样可以写多行。

def sum(num1:int=0, num2:int=0,*nums:int):
    """
    这是一个加法函数
    @param num1 接收第一个加数
    @param num2 接收第二个加数
    @param *nums 可以接受任意多个参数
    @return result 该函数返回相加的结果
    """

    result = 0

    for n in nums:
        result += n

help(sum)

七、作用域

  作用域指的是变量生效的区域。在 python 中一共有 2 中作用域。一种是 全局作用域,另一种是 函数作用域

  全局作用域 在程序执行时创建,在程序结束时销毁。所有函数以外的区域都是全局作用域。在全局作用域中定义的变量,都属于全局变量,全局变量可以在程序的任意位置被访问。

  函数作用域 在函数调用时创建,在调用结束时销毁。函数每调用一次,就会产生一个新的函数作用域。在函数中作用域中定义的变量,都是局部变量,它只能在函数内部被访问。

b = 20

def fun():
    # a定义在函数内部,所以它的作用域就是函数内部,函数外部无法访问
    a = 10
    print("函数内部:a = ", a)
    print("函数内部:b = ", b)

fun()
print("函数外部:b = ", b)

  当我们使用一个变量时,会优先在当前作用域中寻找变量。如果有则使用,如果没有则继续去上一级作用域中去寻找。如果上一级作用域中有则使用,如果依然没有则继续去上级作用域中寻找,以此类推。

def fun1():
    a = 10
    b = 10
    print("fun1中的a:", a)
    print("fun1中的b:", b)

    def fun2():
        a = 20
        print("fun2中的a:", a)
        print("fun2中的b:", b)
    fun2()

fun1()

  如果直至找到全局作用域,依然也没有找到,则会抛出异常:

NameError: name 'c' is not defined

  在函数中为变量赋值时,默认都是为局部变量赋值。如果希望在函数内部修改全局变量,则需要使用 global 关键字来声明变量。

a = 10
b = 10

def fun():
    # 在函数中为变量赋值时,默认都是为局部变量赋值
    a = 20
    # 如果希望在函数内部修改全局变量,则需要使用 global 关键字来声明变量
    global b
    # 修改全局变量
    b = 30
    print("函数内部:a = ", a)
    print("函数内部:b = ", b)

fun()
print("函数外部:a = ", a)
print("函数外部:b = ", b)

建议全局变量大写,局部变量小写;

八、类型提示

  在 Python 3.5 以上的版本函数支持类型提示功能。

def 函数名(形参:形参提示性信息) -> 返回值提示信息:
    ...
def showInfo(name:str,age:int=0) -> dict:
    print(f"name:{name},age:{age}")
    return {"name":name,"age":age}

result = showInfo("Sakura",10)
print(result)
result = showInfo("Sakura")
print(result)

# 可以通过__annotations__查看函数的提示信息
print(showInfo.__annotations__)

九、函数递归

  递归简单理解就是自己引用自己。递归式函数就是在函数中自动调用自己(直接或间接调用函数本身)。递归是解决问题的一种方式,它和循环很像。它的整体思想是,将一个大的问题分解为一个个的小问题,直到问题无法分解时,再去解决问题。

  递归式函数的两个要件:

  1. 基线条件
    • 问题可以被分解为的最小问题,当满足基线条件时,递归就不在执行了
  2. 递归条件
    • 将问题继续分解的条件
def factorial(num):
    # 基线条件,判断num是否为1,如果为1则此时不能再继续递归
    if num == 1:
        return 1
    return num * factorial(num-1)

ressult = factorial(10)
print(ressult)

十、内建函数

  Python 解释器运行时,会自动加载很多的常用的变量、函数以及类,这些就是内建功能或者数据,我们把自动导入的函数称之为 内建函数。我们可以通过 dir(__builtin__) 来查看 Python 解释器运行时自动加载的数据。

【1】、range() 函数

range(start, stop[, step])

  range() 可以用来生成一个自然数的序列,它有三个参数,第一个参数是 起始位置,可以省略,默认是 0,第二个参数是 结束位置,第三个参数是 步长,也可以省略,默认是 1。

  range() 函数返回一个 range 对象,是一个可迭代的对象,可以配合 for 或 next() 等使用。如果想要得到列表,可通过 list() 函数。

a = range(5)
print(a)
print(type(a))
print(list(a))

【2】、map() 函数

map(function, iterable, *iterables)

  map() 函数会根据提供 function 指向的函数对指定的序列 iterable 做映射。参数序列中的每一个元素分别调用 function() 函数,将每次 function() 函数的返回值,存放到序列中当作最后的结果。

a = map(lambda x: x**2, range(1,6))
print(a)
print(type(a))

for item in a:
    print(item)
a = map(lambda x, y: x+y, [1, 2, 3], (4, 5, 6))
print(a)
print(type(a))

for item in a:
    print(item)

【3】、filter() 函数

filter(function, iterable)

  filter() 函数会对序列 iterable 中的每个元素调用 function 指向的函数执行过滤操作。在每次执行时,结果为 True 的序列元素存储到结果中。

a = filter(lambda x : x%2, range(1,6))
print(a)
print(type(a))

for item in a:
    print(item)

十一、函数对象

  在 Python 中,函数是一等对象,它具有以下特点:① 对象是在运行时创建的; ② 能赋值给变量或作为数据结构中的元素; ③ 能作为参数传递; ④ 能作为返回值返回;

def func():
    print("func()函数执行了")

# 可以将函数赋值给一个变量
f = func
print(f)
print(func)

# 通过变量调用函数
f()
def func():
    print("func()函数执行了")

# 可以把函数当作函数的参数传入
def fun(f):
    print(f)
    f()

fun(func)
def func():
    print("func()函数执行了")

# 可以把函数当作函数的返回值返回
def fun():
    return func

res = fun()
print(res)
res()

十二、高阶函数

  高阶函数至少要符合以下两个特点中的一个:① 接收一个或多个函数作为参数; ② 将函数作为返回值返回;

  当我们使用一个函数作为参数时,实际上是将指定的代码传递进了目标函数。

def filter(list, fun):
    new_list = []
    for n in list:
        if fun(n):
            new_list.append(n)
  
    return new_list

def IsEven(num):
    return num %2 == 0

def IsGreater5(num):
    return num > 5

list = [x for x in range(11)]

new_list = filter(list,IsEven)
print(new_list)

new_list = filter(list,IsGreater5)
print(new_list)

十三、匿名函数

  匿名函数也称为 lambda 函数表达式,lambda 表达式专门用于创建一些简单的函数,它是函数创建的又一种方式。它的语法格式如下:

lambda 参数列表 : 返回值

  我们可以通过如下方法调用 lambda 函数表达式:

(lambda 形参列表 : 返回值)(实参列表)
print(lambda a, b : a + b)
print((lambda a, b : a + b)(10, 20))

我们也可以将一个匿名函数赋值给变量。

def filter(list, fun):
    new_list = []
    for n in list:
        if fun(n):
            new_list.append(n)
  
    return new_list

list = [x for x in range(11)]

new_list = filter(list, lambda num : num % 2 == 0)
print(new_list)

new_list = filter(list, lambda num : num > 5)
print(new_list)

匿名函数一般都是作为参数使用;

如果匿名函数不需要形参,可以书写如下格式:lambda : 表达式

匿名函数默认有 return;

posted @ 2024-09-24 19:38  星光映梦  阅读(5)  评论(0编辑  收藏  举报