Python自定义函数

一个规范的Python程序,除非代码量很少,基本都应该由多个函数组成,这样的代码才更加模块化、规范化

函数是Python程序中不可或缺的一部分,Python有很多内置函数

  • print():输出函数,将括号中的内容打印到控制台上
  • len():返回序列的长度(元素个数)
  • range():生成一个整数序列,通常用在for循环中

平时开发中,我们更多的需要自定义函数,本文详细讲解下Python的自定义函数

函数基础

函数就是为了实现某一功能的代码段,只要写好以后,就可以重复利用

python 复制代码
def my_func(message):
    print('Got a message: {}'.format(message))

# 调用函数 my_func()
my_func('Hello World')
# 输出
Got a message: Hello World
  • def 是函数的声明
  • my_func 是函数的名称
  • 括号里面的 message 则是函数的参数
  • print 那行则是函数的主体部分,可以执行相应的语句
  • 在函数最后,你可以返回调用结果(return 或 yield),也可以不返回。
python 复制代码
def name(param1, param2, ..., paramN):
    statements
    return/yield value # optional

def

和编译型语言(比如C语言)不一样的是,def是可执行语句,这意味着函数直到被调用前,都是不存在的

当程序调用函数时,def语句才会创建一个新的函数对象,并赋予其名字

要注意,主程序调用函数时,必须保证这个函数此前已经定义过,不然会报错

python 复制代码
my_func('hello world')
def my_func(message):
    print('Got a message: {}'.format(message))
    
# 输出
NameError: name 'my_func' is not defined

但如果我们在函数内部调用其他函数,函数间哪个声明在前、哪个在后就无所谓,因为def是可执行语句,函数在调用之前都不存在,我们只需保证调用时,所需的函数都已经声明定义就行

python 复制代码
def my_func(message):
    my_sub_func(message) # 调用my_sub_func()在其声明之前不影响程序执行
    
def my_sub_func(message):
    print('Got a message: {}'.format(message))

my_func('hello world')

# 输出
Got a message: hello world

参数

Python函数的参数可以设定默认值

python 复制代码
def func(param = 0):
    ...

这样在调用函数func()时,如果参数param没有传入,则参数默认为0,如果传入了参数param,就会覆盖默认值

Python与其他语言相比的一大特点是dynamically typed,可以接受任何数据类型(整型、浮点、字符串等),函数参数也同样适用

python 复制代码
def my_sum(a, b):
    return a + b

print(my_sum(3, 5)) # 输出8

print(my_sum([1, 2], [3, 4])) # 输出[1, 2, 3, 4]

print(my_sum('hello ', 'world')) # 输出hello world

如果两个参数的数据类型不同,比如一个是列表、一个是字符串,两者无法相加

python 复制代码
print(my_sum([1, 2], 'hello'))
TypeError: can only concatenate list (not "str") to list

Python不用考虑输入的数据类型,而是将其交给具体的代码去判断执行,编程语言中,这种行为称为多态,这种方便的特性,在实际使用中也会带来诸多问题,必要时在开头加上数据的类型检查

函数的嵌套

函数嵌套就是指函数里面又有函数,Python支持函数的嵌套

python 复制代码
def f1():
    print('hello')
    def f2():
        print('world')
    f2()
f1()

# 输出
hello
world

函数嵌套能够保证内部数据的隐私。内部函数只能被外部函数所调用和访问,不会暴露在全局作用域,因此如果函数内部有一些隐私数据(比如数据库的用户、密码),不想暴露在外,就可以使用函数的嵌套,将其封装在内部函数中,只通过外部函数来访问

python 复制代码
def connect_DB():
    def get_DB_configuration():
        ...
        return host, username, password
    conn = connector.connect(get_DB_configuration())
    return conn

这里的函数 get_DB_configuration,便是内部函数,它无法在 connect_DB() 函数以外被单独调用

python 复制代码
get_DB_configuration()

# 输出
NameError: name 'get_DB_configuration' is not defined

合理的使用函数嵌套,能够提高程序的运行效率

python 复制代码
def factorial(input):
    # validation check
    if not isinstance(input, int):
        raise Exception('input must be an integer.')
    if input < 0:
        raise Exception('input must be greater or equal to 0' )
    ...

    def inner_factorial(input):
        if input <= 1:
            return 1
        return input * inner_factorial(input-1)
    return inner_factorial(input)


print(factorial(5))

我们使用递归的方式计算一个数的阶乘。因为在计算之前,需要检查输入是否合法,所以我们写成函数嵌套的形式,这样输入是否合法就只用检查一次,而如果我们不是用函数嵌套,那么每调用一次递归便会检查一次,这是没有必要的,也会降低程序的运行效率,实际工作中,如果你遇到相似的情况,输入检查不是很快,还会耗费一定的资源,那么运用函数的嵌套就十分必要了。

以下是计算factorial(5)时函数的执行过程:

  1. 首先调用factorial(5)。
  2. factorial函数进行输入验证,确认5是一个整数且大于等于0。
  3. factorial函数调用内部函数inner_factorial(5)。
  4. inner_factorial函数检查输入是否小于或等于1。由于5不满足条件,执行递归调用。
  5. inner_factorial递归调用自身,参数变为inner_factorial(4)。
  6. 这个过程一直重复,直到到达基本情况(输入小于或等于1)。
  7. 当inner_factorial被调用时,参数为1,返回1。
  8. 然后,之前的递归调用开始返回结果,通过将当前返回值与之前的结果相乘来计算阶乘。
  9. 最终,factorial(5)的调用返回结果。
python 复制代码
factorial(5)
inner_factorial(5) = 5 * inner_factorial(4)
inner_factorial(4) = 4 * inner_factorial(3)
inner_factorial(3) = 3 * inner_factorial(2)
inner_factorial(2) = 2 * inner_factorial(1)
inner_factorial(1) = 1

return和yield

  • return语句用于从函数中返回值并终止函数的执行。
    • 当函数执行到return语句时,它会立即停止并返回一个值。
    • 在函数中,return语句可以出现多次,但只有第一次出现时才会返回值。如果没有指定返回值,则默认返回None。
  • yield语句用于定义生成器函数。
    • 生成器函数是一种特殊的函数,它可以暂停执行并返回一个中间结果,然后在需要时恢复执行。
    • 当函数执行到yield语句时,它会返回一个值并暂停执行,等待下一次调用继续执行。
    • 在函数中,yield语句可以出现多次,每次返回一个值。
    • 生成器函数可以用于迭代器,可以通过for循环或next()函数来逐个获取生成器函数返回的值。

return和yield的主要区别在于:return语句用于从函数中返回值并终止函数的执行,而yield语句用于定义生成器函数,可以暂停执行并返回一个中间结果。

python 复制代码
# return语句的例子
def func_return():
    print("start")
    return 1
    print("end")  # 这行代码不会执行

result = func_return()
print(result)  # 输出:1

# yield语句的例子
def func_yield():
    print("start")
    yield 1
    print("middle")
    yield 2
    print("end")

gen = func_yield()
print(next(gen))  # 输出:1
print(next(gen))  # 输出:2

函数变量作用域

如果变量是在函数内部定义的,就称为局部变量,只在函数内部有效。一旦函数执行完毕,局部变量就会被回收,无法访问

python 复制代码
def read_text_from_file(file_path):
    with open(file_path) as file:
        ...

在函数内部定义了file这个变量,这个变量只在read_text_from_file这个函数里有效,在函数外部则无法访问

全局变量则是定义在整个文件层次上的

python 复制代码
MIN_VALUE = 1
MAX_VALUE = 10
def validation_check(value):
    if value < MIN_VALUE or value > MAX_VALUE:
        raise Exception('validation check fails')

这里的 MIN_VALUE 和 MAX_VALUE 就是全局变量,可以在文件内的任何地方被访问,当然在函数内部也是可以的

但我们不能在函数内部随意改变全局变量的值

python 复制代码
MIN_VALUE = 1
MAX_VALUE = 10
def validation_check(value):
    ...
    MIN_VALUE += 1
    ...
validation_check(5)

UnboundLocalError: local variable 'MIN_VALUE' referenced before assignment

这是因为Python的解释器会默认函数内部的变量为局部变量,但又发现局部变量MIN_VALUE 并没有声明,因此就无法执行相关操作

如果我们一定要在函数内部改变全局变量的值,就必须加上global声明

python 复制代码
MIN_VALUE = 1
MAX_VALUE = 10
def validation_check(value):
    global MIN_VALUE
    ...
    MIN_VALUE += 1
    ...
validation_check(5)

这里的 global 关键字,并不表示重新创建了一个全局变量 MIN_VALUE,而是告诉 Python 解释器,函数内部的变量 MIN_VALUE,就是之前定义的全局变量,并不是新的全局变量,也不是局部变量。这样,程序就可以在函数内部访问全局变量,并修改它的值了。

如果遇到函数内部局部变量和全局变量同名的情况,那么在函数内部,局部变量会覆盖全局变量

python 复制代码
MIN_VALUE = 1
MAX_VALUE = 10
def validation_check(value):
    MIN_VALUE = 3
    ...

在函数 validation_check() 内部,我们定义了和全局变量同名的局部变量 MIN_VALUE,那么,MIN_VALUE 在函数内部的值,就应该是 3 而不是 1 了。

对于嵌套函数来说,内部函数可以访问外部函数定义的变量,但是无法修改,若要修改,必须加上 nonlocal 这个关键字

python 复制代码
def outer():
    x = "local"
    def inner():
        nonlocal x # nonlocal关键字表示这里的x就是外部函数outer定义的变量x
        x = 'nonlocal'
        print("inner:", x)
    inner()
    print("outer:", x)
outer()
# 输出
inner: nonlocal
outer: nonlocal

如果不加上 nonlocal 这个关键字,而内部函数的变量又和外部函数变量同名,那么同样的,内部函数变量会覆盖外部函数的变量。

python 复制代码
def outer():
    x = "local"
    def inner():
        x = 'nonlocal' # 这里的x是inner这个函数的局部变量
        print("inner:", x)
    inner()
    print("outer:", x)
outer()
# 输出
inner: nonlocal
outer: local

闭包

闭包和嵌套函数类似,不同的是闭包的外部函数返回的是一个函数,而不是一个具体的值

返回的函数通常赋于一个变量,这个变量可以在后面被继续执行调用

比如我们想计算一个数的 n 次幂,用闭包可以写成下面的代码:

python 复制代码
def nth_power(exponent):
    def exponent_of(base):
        return base ** exponent
    return exponent_of # 返回值是exponent_of函数

square = nth_power(2) # 计算一个数的平方
cube = nth_power(3) # 计算一个数的立方 
square
# 输出
<function __main__.nth_power.<locals>.exponent(base)>

cube
# 输出
<function __main__.nth_power.<locals>.exponent(base)>

print(square(2))  # 计算2的平方
print(cube(2)) # 计算2的立方
# 输出
4 # 2^2
8 # 2^3

这里外部函数 nth_power() 返回值,是函数 exponent_of(),而不是一个具体的数值。

在执行完square = nth_power(2)和cube = nth_power(3)后,外部函数 nth_power() 的参数 exponent,仍然会被内部函数 exponent_of() 记住

这样之后我们调用 square(2) 或者 cube(2) 时,程序就能顺利地输出结果,而不会报错说参数 exponent 没有定义了

为什么要闭包呢?上面的程序,也可以写成下面的形式

python 复制代码
def nth_power_rewrite(base, exponent):
    return base ** exponent

使用闭包的一个原因,是让程序变得更简洁易读。设想一下,如果我们需要计算很多个数的平方,那么写成下面哪一种形式更好呢?

python 复制代码
# 不适用闭包
res1 = nth_power_rewrite(base1, 2)
res2 = nth_power_rewrite(base2, 2)
res3 = nth_power_rewrite(base3, 2)
...

# 使用闭包
square = nth_power(2)
res1 = square(base1)
res2 = square(base2)
res3 = square(base3)
...
  • 首先直观来看,第二种形式,让你每次调用函数都可以少输入一个参数,表达更为简洁。
  • 其次,和上面讲到的嵌套函数优点类似,函数开头需要做一些额外工作,而你又需要多次调用这个函数时,将那些额外工作的代码放在外部函数,就可以减少多次调用导致的不必要的开销,提高程序的运行效率。

总结

  1. Python中函数的参数可以接受任意的数据类型,使用起来需要注意,必要时请在函数开头加入数据类型的检查
  2. Python中函数的参数可以设定默认值
  3. 嵌套函数的使用,能保证数据的隐私性,提高程序运行效率
  4. 合理地使用闭包,则可以简化程序的复杂度,提高可读性

参考资料:极客时间《Python 核心技术与实战》

相关推荐
凤枭香1 分钟前
Python OpenCV 傅里叶变换
开发语言·图像处理·python·opencv
测试杂货铺8 分钟前
外包干了2年,快要废了。。
自动化测试·软件测试·python·功能测试·测试工具·面试·职场和发展
艾派森13 分钟前
大数据分析案例-基于随机森林算法的智能手机价格预测模型
人工智能·python·随机森林·机器学习·数据挖掘
小码的头发丝、39 分钟前
Django中ListView 和 DetailView类的区别
数据库·python·django
Chef_Chen1 小时前
从0开始机器学习--Day17--神经网络反向传播作业
python·神经网络·机器学习
千澜空2 小时前
celery在django项目中实现并发任务和定时任务
python·django·celery·定时任务·异步任务
斯凯利.瑞恩2 小时前
Python决策树、随机森林、朴素贝叶斯、KNN(K-最近邻居)分类分析银行拉新活动挖掘潜在贷款客户附数据代码
python·决策树·随机森林
yannan201903132 小时前
【算法】(Python)动态规划
python·算法·动态规划
蒙娜丽宁2 小时前
《Python OpenCV从菜鸟到高手》——零基础进阶,开启图像处理与计算机视觉的大门!
python·opencv·计算机视觉
光芒再现dev2 小时前
已解决,部署GPTSoVITS报错‘AsyncRequest‘ object has no attribute ‘_json_response_data‘
运维·python·gpt·语言模型·自然语言处理