一、Python函数的形式
def function_name (参数, ...) -> return value_type:# 函数体return value# 看具体需求# 如果没有return语句,函数执行完毕后也会返回结果# 只是结果为None。return None可以简写为return
二、函数名
这里可以与C++中进行类比,C++的函数名表示函数的地址,可以通过指针来指向函数的地址,再以指针调用函数:
#include <iostream>
void func(int a, int b)
{std::cout << a << " + " << b << " = " << a + b << std::endl;
}
int main()
{void (*f) (int, int) = func;f(1,2);return 0;
}
Python中的函数也是一样的道理,函数名是指函数的本身,可以被变量名引用,变量名就可以充当新的函数名使用:
my_abs = abs;
print(my_abs(-10))
def add(a, b):return a + bmy_add = add;
print(my_add(1, 2))
print(my_add('abc', 'ABC'))
所以函数名也是一个对象,我们可以使用函数名来充当函数的参数,从而实现类似于C语言在函数的参数中传递函数指针的操作,完成回调
三、 Python的返回值
【注】:每次返回都是返回的一个新的对象
在Python3中,提供了可以标注函数返回值的类型的功能,但Python是动态类型语言对返回值的类型标注不会像C++这种静态类型语言那样进行严格的类型检查和限制,所以我们可以认为返回值只是给程序员自己看着方便,在语法不会检查。
返回值标注的作用:
- 增强代码的可读性:标注返回值类型可以让其他开发者在阅读代码时更清楚地理解函数的预 期输出。
- 作为文档:有助于为使用该函数的其他开发者提供明确的信息,减少误解和错误使用。
- 辅助静态类型检查工具:虽然 Python 本身不强制检查,但结合一些第三方的静态类型检查 工具(如 mypy),可以在一定程度上进行类型检查,提前发现潜在 的类型错误。
【C++】
在C++中,函数的返回值类型是必须标注的,静态类型语言必须保证接收函数返回值的时候数据类型是匹配的
1. Python函数返回多个值
返回的是一个元组对象,在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值,所以,Python的函数返回多值其实就是返回一个tuple,但写起来更方便;
而C++中返回多个值,可以采用输出型参数或者是返回一个类对象
def function_name (参数, ...):return a, b ,c
如果函数的返回值我们只使用一部分,不关注其他的返回值,可以使用占位符 ‘ _ ’来进行占位
def func():return 1, 2, 3_, _, a = func()
print('a = %d' % a)
2. 函数名作为返回值
以函数名作为返回值的函数,被调用时不立刻实现功能,返回一个内部函数名,在需要实现时,使用返回值(内部函数名)来完成功能的实现:
如:完成一个从1到num 的累加函数
def lazy_sum(num):def quick_sum():i = 1all_num = 0while i <= num:all_num += ii += 1return all_numreturn quick_sumtotal = lazy_sum(100) # 返回一个求和函数,不会直接求和
t = total() # 在调用该求和函数的时候才会求和
print(t)
内部函数quick_
sum
可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum
返回函数quick_sum
时,相关参数和变量都保存在返回的函数中。(此时的相关变量都是最后更新的变量值)
如何证明 内部函数 使用的变量是 外部函数提供的最新变量值?
def first_func():lister = []for i in range(1, 4):def second_func():lister.append(i)return listerreturn second_funcx = first_func()
y = first_func()
z = first_func()
print(x())
print(y())
print(z())
在使用该函数的时候,我们在循环中实现了一个内部函数second_func,具体是想让其插入1到3的数,可是输出结果是只有3的,这与我们的预期不符。
原因如下:
在调用外部函数first_fun的时候,返回的是一个内部函数名second_func,但是不会被立刻执行,只有在我们显式地调用时,才会实现该函数,而外部函数执行结束,相关变量都被保存在内部函数中,此时的循环i已经自增到3了,但是内部函数根本没有实现,所以在我们显式地调用时,用到的i值为3,才会出现我们看到的现象
所以当函数名作为返回值的时候,不要引用任何循环变量,或者后续会发生变化的变量
如果内部函数对外部函数的变量进行写操作(修改),则会报错,因为在修改的时候,Python解释器会将变量当成内部函数的局部变量,而变量名必须初始化,所以会报错,你没有初始化它,为了解决这一问题,需要在内部函数加一个
nonlocal x
的声明。加上这个声明后,Python解释器把x
看作外层函数的局部变量,它已经被初始化了,可以正确计算。
二、Python函数的参数
1. 位置参数:
与C++一致,实参的位置与形参的位置相匹配
2. 缺省参数:
与C++一致,形参有缺省参数,可以不传参也可以传参
设置缺省参数的规则:
- 参数在前,缺省参数在后
- 当函数有多个参数时,在传参时把变化大的参数放在前面,变化小的参数放在后面(变化小的参数可以作为缺省参数)
- 必须指向不可变对象
为什么默认参数在后?
当默认参数在前时,我们传入一个实参,就会发生疑问,这个实参是给缺省参数的?还是给参数的?因为可以一方面通过位置参数来,另一方面可以通过缺省参数来,所以就会产生二义性,为了防止此类事件的发生,规定缺省参数要在参数的后面。
缺省参数的坑:为什么要指向不可变对象?
Python函数缺省参数的 “ 坑 ” (与C++对比学习)-CSDN博客
3. 可变参数:
支持传入任意个参数,函数内部接收到的是一个元组tuple(在返回值和参数这里使用元组主要是保证传入或输出的对象的安全性,防止恶意篡改)
使用形式:
def func(*num):s = 0for i in num:s += ireturn sret = func(1, 2, 3, 4, 5, 6, 7, 8)
print(ret)
4. 显示命名参数
按照形参名字传参,无视位置,一般搭配缺省参数使用
使用形式:
def func(id_num, name='dd', age=20):print('id:', id_num, '\nname:', name, '\nage:', age)func(1, age=18)
5. 关键字参数
关键字参数允许我们传入任意个含有参数名的参数,这些关键字参数会在函数内部自动组装为一个dict。
使用形式:
def func(name, age, **kw):print('name:', name, '\nage:', age, '\nothers:', kw)func('dd', 20, city='上海', work='程序员')
简化版本:
**dic表示把该字典中的所有key:value用关键字参数传入函数的**kw中,需要注意的是kw获得的dict是dic的一份拷贝,对kw的改动不会影响函数外的dic
def func(name, age, **kw):print('name:', name, '\nage:', age, '\nothers:', kw)dic = {'city': '上海', 'work': '程序员'}
func('yy', 18, **dic)
6. 命名关键字参数
只可以传入命名关键字参数后的关键字参数
使用形式:
- 特殊分隔符 * 后面的参数被视为命名关键字参数
- 如果函数定义中有可变参数,后面跟着的命名关键字参数就不需要特殊分隔符*
- 命名关键字参数必须传入参数名
def fun(name, age, *, city, work):print('name:', name, '\nage:', age, '\ncity:', city, '\nwork', work)fun('wyd', 20, city='上海', work='程序员')
7. 各种参数可以组合使用
参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数
三、递归函数
与C++一致,自己调用自己,但是要防止栈溢出,采用的方法是尾递归,保证每次函数调用只占用一个栈帧。
如计算n的阶乘:
采用递归的方式:
n值过大,则会导致栈空间溢出,因为下面这种是不断调用递归,会不断的压栈,从而导致栈空间不足
def fact(n):if n == 1:return nreturn n * fact(n-1)
采用尾递归的方式:
每次只用一个栈帧,尾递归的好处就是最外层的函数调用完自身就会销毁函数栈帧,从而保证每次只使用一个栈帧,遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的
fact(n)
函数改成尾递归方式,也会导致栈溢出。
def fact_operator(n, s):if n == 1:return sreturn fact_operator(n - 1, n * s)def fact(n):return fact_operator(n, 1)
四、 空函数
def func():pass
C++的空函数只需要不写任何函数体
void func()
{
}
五、匿名函数lambda
lambda表达式返回的是一个函数对象,所以一般我们要用变量名接收,再调用。
表达式计算的结果就相当于普通函数的返回值
lambda 参数: 表达式
my_sum = lambda x, y: x + y
print(my_sum)
print(my_sum(3, 5))
如同下面的函数:
def add(x, y):return x + ymy_sum = add
print(my_sum(3, 5))
C++中也有lambda表达式,相对于Python的更复杂,lambda表达式返回的是一个函数对象
#include <iostream>
int main()
{auto func = [](int a, int b){return a + b;};std::cout << func(1,2) << std::endl;;return 0;
}
六、偏函数
new_func_name = functools.partial(old_func_name, 参数=?,...)
functools.partial
本质就是设置给参数一个缺省值,返回一个新的函数,从而便于我们调用,但在调用的时候仍然可以对新函数中设置过缺省值的参数进行传参
import functoolsint2 = functools.partial(int, base=2) # 创建一个专门用于二进制序列转换为十进制的函数
print(int2('111'))
print(int2('1234', base=10)) # 仍然可以传参
类似C++中对函数的封装,减少函数参数的个数,对程序员屏蔽复杂的参数传递,只展现简单的接口
七、函数对象的属性
-
__name__
:函数的名称。 -
__doc__
:函数的文档字符串(如果有)。 -
__module__
:函数所属的模块名。 -
__defaults__
:包含函数默认参数值的元组。 -
__code__
:与函数相关的代码对象,包含函数的字节码等信息。 -
__globals__
:函数所在模块的全局命名空间的字典。 -
__closure__
:如果函数是闭包,此属性包含闭包所使用的单元格对象。
def function(x):"""This is a sample function"""return x + 1print(function.__name__)
print(function.__doc__)
八、装饰器decorator
装饰器可以在代码运行期间,既不改变函数本身的定义,又可以动态增添函数功能。装饰器本质就是一个返回函数名的高阶函数。
如,我们给添加一个调用函数时,自动输出日志的功能:
def log(func):def wrapper(*args):print('call %s()' % func.__name__)return func(*args)return wrapper@log
def student(name, age, score):print("name = %s, age = %d, score = %.2f," % (name, age, score))student('dd', 20, 100)
装饰器中调用函数的流程
所以我们为什么要在wrapper函数中写可变参数列表,主要是为了迎合student的传参,否则会导致参数不匹配,调用失败。