目录
Python函数是程序设计中的基本构造块,它封装了可重复使用的代码,并允许以模块化的方式来组织和执行任务。下面我们从基础到进阶对Python函数进行详解:
函数基础
一、定义函数
定义一个自己想要功能的函数,以下是简单规则:
- 1、函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()。
- 2、任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
- 3、函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
- 4、函数内容以冒号 : 起始,并且缩进。
- 5、return [表达式] 结束函数,选择性地返回一个值给调用方,不带表达式的 return 相当于返回 None。
def 函数名(参数1:int, 参数2:int) ->int:
'''
:param 参数1: 一个整数
:param 参数2: 一个整数
:return: 返回整数
'''
和:int=参数1+参数2
return 和
三要素:1、函数名 2、参数 3、返回值
参数名接冒号 : 以及 -> 都是说明参数和返回值的类型,没有约束效果,方便理解代码
二、使用函数
- 函数名后接( ) 括号表示调用函数
- 括号内传递参数(如果定义函数没有设置参数就不用传)
- return 后接变量,通过return变量,接收函数的返回值,一个函数可以返回多个值。这些值通常以元组(tuple)的形式返回,如果有较多要返回的数据,我们也可以通过容器存放比如列表、字典。
def sum1(parm1:float,parm2:float) ->float:
'''
:param parm1: 一个浮点数
:param parm2: 一个浮点数
:return: 返回浮点数
'''
sum=Decimal(f'{parm1}')+Decimal(f'{parm2}')
return sum
print(sum1(1.1,2.2)) #输出 3.3
def calculate(parm1:int,parm2:int):
multi=parm1*parm2
div=parm1/parm2
sub=parm1-parm2
return multi,div,sub
print(calculate(1,2)) #输出 (2, 0.5, -1)
def calculate(parm1,parm2):
multi = parm1 * parm2
div = parm1 / parm2
sub = parm1 - parm2
dic_result={'multi':multi,'div':div,'sub':sub}
return dic_result
print(calculate(1,2))#输出 {'multi': 2, 'div': 0.5, 'sub': -1}
三、函数的参数
1、位置参数
指按照位置传入的参数,默认情况下参数值和参数名称是按函数声明中定义的顺序匹配起来的:
def 函数(calculate:str, b:int, c:int)->int:
if calculate =='+':
结果= b + c
elif calculate =='-':
结果= b - c
else:
结果='输入正确符号'
return 结果
aa=函数('-',1,2) #参数的位置,决定接收参数的参数名,calculate='-' b=1 c=2
print(aa) #输出 -1
2、关键字参数
关键字参数是指按照参数名传入的参数,函数调用使用关键字参数来确定传入的参数值。使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。传参的时候位置参数必须在前,关键字参数在后,不然会报错。
def 函数(a,b):
return a-b
结果=函数(3,4) #位置参数 a=3,b=4
结果1=函数(b=3,a=4) #关键字参数 b=3,a=4
结果2=函数(3,b=4) #位置参数 a=3,关键字参数 b=4
print(结果,结果1,结果2) #输出 -1 1 -1
结果3=函数(a=4,3) #位置参数必须放在关键字参数前面。不然会报错
#报错
'''
结果3=函数(a=4,3)
^
SyntaxError: positional argument follows keyword argument
'''
3、默认参数
在定义函数的时候,给参数设置默认值。调用函数时,如果没有传入该参数,则自动使用默认值;如果传入参数,则使用传入的参数值
def 函数(a=1,b=1):
print(a-b)
结果=函数() # 使用默认参数a=1,b=1输出0
结果1=函数(3,2) # 使用传入参数a=3,b=2输出1
4、不定长参数
让函数接收长度不确定的参数:0~n个参数。可变参数*args 可接收任意数量的位置参数,这些参数会自动封装成一个元组;关键字可变参数**kwargs 可接收任意数量的关键字参数,这些参数会自动封装成一个字典。
def 函数(*args,**kwargs):
print(type(args),args)
print(type(kwargs),kwargs)
a=函数(1,2,3,3,B=1,C=2)
# 输出:
'''
<class 'tuple'> (1, 2, 3, 3)
<class 'dict'> {'B': 1, 'C': 2}
'''
a=函数()#如果不传入参数则输出空元组、空字典
# 输出:
'''
<class 'tuple'> ()
<class 'dict'> {}
'''
5、强制关键字参数
也叫命名关键字参数,有时候,我们需要关键字参数的传入,但是又不想让它肆无忌惮的传入,这时候就需要限制一下哪些是可以接收的关键字参数,主要的用法有以下几种:
#1、我们可以在位置参数之后加一个*,后面的参数都被视作是强制命名关键字参数。
def get(name, age, *, city, job):
print(name, age, city, job)
get('张三', 18, city='深圳', job='码农') # 输出: 张三 18 深圳 码农
get('张三', 18, '深圳', '码农') # '北京' '码农' 没有按关键字参数形式传参会报错
'''
get('张三', 18, '深圳', '码农') # 输出: 张三 18 深圳 码农
TypeError: get() takes 2 positional arguments but 4 were given
'''
#2、当我们参数中存在可变参数*args时,那么就不需要再添加*来作分割。
def get1(name, age, *args, city, job):
print(name, age, args, city, job)
get1('张三', 18, 1, 2, 3, city='深圳', job='码农') # 输出: 张三 18 (1, 2, 3) 深圳 码农
#3、命名关键字参数是可以设置默认值的。
def get2(name, age, *, city='XA', job):
print(name, age, city, job)
get2('张三', 18, job='码农') # 默认值输出: 张三 18 XA 码农
get2('张三', 18, city='深圳', job='码农')# 传参输出: 张三 18 深圳 码农
注意:
- 1、命名关键字必须要传入参数名,除非其为默认参数;例如上述示例2,必须用city=‘xxx’来传入命名关键字参数,否则会报错。
- 2、缺少*时,命名关键字参数被视为位置参数。
6、强制位置参数
Python3.8 新增了一个函数形参语法 / 用来指明函数形参必须使用指定位置参数,不能使用关键字参数的形式。
#在以下的例子中,形参a必须使用指定位置参数,b可以是位置形参或关键字形参,而c要求为关键字形参
def f(a, /, b, *, c):
print(a, b, c)
f(10, 20, c=30) # 输出: 10 20 30
f(10, b=20, c=30) # 输出: 10 20 30
# f(a=10, b=20, c=30) # a 不能使用关键字参数的形式
'''报错:
f(a=10, b=20, c=30) # a 不能使用关键字参数的形式
TypeError: f() got some positional-only arguments passed as keyword arguments: 'a'
'''
f(10, 20, 30) # c 必须使用关键字参数的形式
'''报错:
f(10, 20, 30) # c 必须使用关键字参数的形式
TypeError: f() takes 2 positional arguments but 3 were given
'''
四、函数的返回值
return [表达式] 语句用于退出函数,选择性地向调用方返回一个表达式。不带参数值的 return 语句返回 None。前面使用函数例子中已有关于return的使用。
函数进阶
五、函数的本质
函数是一个对象,python中一切皆对象:数据、函数、类、模块、包;函数的核心本质是代码组织和抽象的重要工具,它促进了代码的模块化、清晰度以及复用能力,同时也体现了面向过程和函数式编程范式的精髓。
def a():
bb=3
return bb
b=a()
print(b) # 输出: 3
del a #删除函数a
a() #再调用会报错
'''
a() #再调用会报错
NameError: name 'a' is not defined
'''
从数据的角度来看,函数可以被修改名字、可以传递、也可以被删除
六、函数的嵌套
嵌套函数是指在一个函数的内部定义一个新的函数,函数的嵌套是指在一个函数内部定义另一个或多个函数。这种结构允许内层函数访问外层函数的作用域(包括变量和参数),从而形成了一种封闭作用域(闭包)。
def outer(*args,**kwargs):
name="小白"
def inner(a,b):
print(name)
return a+b
return inner
new_fuc=outer() #outer返回值保存到new_fuc
print(new_fuc(1,2))
上述例子里 outer是外层函数,1、inner是内嵌于outer中的内层函数;2、当outer被调用时,它首先执行内部逻辑,并返回inner函数对象;3、内层函数 inner可以访问到其外部函数 outer 的局部变量 name,即使在 outer执行完毕后,只要 inner仍然存在并被调用,这种访问能力仍会保留。
函数嵌套的一个重要应用是实现闭包,即一个函数及其相关的引用环境组合在一起构成的功能单元。闭包使得内层函数能够记住并使用在其定义时刻的状态信息,即使在其创建的上下文已经不存在的情况下依然可以操作这些状态。
七、函数的相互调用
函数的相互调用指一个函数调用另一个函数,或者一个函数调用自身(递归调用)。函数的相互调用可以让程序更加模块化、可读性更高,因为可以将一个大问题拆分成多个小问题,每个小问题都由一个函数来解决。
def add(x, y):
return x + y
def multi(x, y):
result = 0
for i in range(y):
result = add(result, x)
return result
result = multi(3, 4)
print(result) # 输出12
add 函数用于计算两个数的和,multi 函数用于计算两个数的乘积。在 multi 函数内部,通过循环调用 add 函数来实现乘法运算,最终返回结果 12。
八、变量的作用域(重要)
在Python代码中,无法直接修改内置作用域中的名字,但可以创建同名的新局部变量覆盖其影响。若要查看所有内置名称,可以使用 builtins
模块(在Python 3.x 中),或者通过调用 dir(__builtins__)
来列出:
-
Local局部作用域(局部变量)
- 在函数、方法或者代码块内部定义的变量具有局部作用域,局部变量仅在定义它的那个函数或代码块内有效,当函数执行完毕后,局部变量会被销毁。
def func1(): local_var = "我是局部变量" print(local_var) func1() # 输出: 我是局部变量 print(local_var) # 函数内的局部变量在函数外不能调用 #报错:NameError: name 'local_var' is not defined
-
Enclosed 闭包作用域
- 存在于函数和函数之间,当一个内部函数引用了外部函数的局部变量时,即使外部函数已经执行完毕,只要内部函数仍然存在并被调用,那么这个变量就会保持活动状态,形成了闭包;子函数使用父函数的数据时,有可能出现数据泄露,最安全是使用本地作用域的数据。
def outer_func(x): def inner_func(y): return x + y return inner_func add_5 = outer_func(5) #给outer_func传递的参数为5即x=5 这时inner_func中的x值为5 print(add_5(3)) #再给add_5传递的参数为3即y=3 # 输出: 8 (因为inner_func仍然可以访问outer_func中的x值) def create_data(): big_data = [x for x in range(10**10)] # 创建一个占用大量内存的大列表 def access_big_data(): return big_data # 内部函数引用外部函数的作用域中的big_data # 返回内部函数作为闭包 return access_big_data # 创建并返回一个闭包 data = create_data() # 此时,尽管create_data函数执行完毕, # 但由于闭包access_big_data仍然持有对big_data的引用, # 所以big_data不会被垃圾回收,从而导致内存泄漏。 # 如果类似这样的闭包创建了很多,并且不再需要它们所引用的大数据, # 则会持续占用内存资源,特别是在长时间运行的服务或循环结构中, # 这种未释放的内存积累可能会造成严重的问题。
-
Global全局作用域
- 在函数、类或其他代码块外部定义的变量具有全局作用域;全局变量在整个程序文件(模块)内都是可见的,任何函数或方法都可以访问到它;在Python中,如果在函数内部需要改变全局变量的值,通常需要使用
global
关键字声明。# 在模块级别定义全局变量 global_var = "我是全局变量" def func(): print(global_var) # 可以直接读取到全局变量 func() # 输出: 我是全局变量 # 若要在函数内部修改全局变量 def modify_global(): global global_var # 使用global关键字声明全局变量 global_var = "我是函数内声明的全局变量" modify_global() print(global_var) # 输出: 我是函数内声明的全局变量
-
内置作用域(Built-in Scope)
- 是指预定义的、全局可用的一组变量和函数,这些变量和函数是Python解释器启动时就自动加载的一部分。内置作用域中的名称可以直接在任何模块或函数内部使用,无需导入任何模块。
-
例如,以下是一些Python内置作用域内的对象:
- 内置函数:如
print()
用于打印输出内容,len()
用于计算序列长度等。 - 内置异常类:如
Exception
、TypeError
、ValueError
等。 - 内置常量:如
True
、False
和None
。 - 内置类型:如
int
、str
、list
、dict
、tuple
等基本数据类型及其构造函数。import builtins # 输出部分内置名称 for name in dir(builtins): if not name.startswith("__"): print(name)
类作用域(类变量和实例变量)
- 类变量通过类名来访问,对所有类实例共享。实例变量通过实例对象(self)来访问,每个实例都有自己的独立副本。
class MyClass: class_var = "我是类变量" # 类变量 def __init__(self, instance_var): self.instance_var = instance_var # 实例变量 obj1 = MyClass("实例变量1") obj2 = MyClass("实例变量2") print(MyClass.class_var) # 类调用类变量,输出: 我是类变量 print(obj1.class_var) # obj1实例调用类变量,输出: 我是类变量 print(obj2.class_var) # obj2实例调用类变量,输出: 我是类变量 print(obj1.instance_var) # obj1实例调用传入的实例参数,输出: 实例变量1 print(obj2.instance_var) # obj2实例调用传入的实例参数,输出: 实例变量1
九、匿名函数
http://t.csdnimg.cn/XxwW3 有写过关于匿名函数lambda的文章,感兴趣可以查阅
十、递归函数
递归函数是Python中一种非常强大的编程技术,它是指在函数内部调用自身的一种编程方法。递归函数通常用来解决那些可以通过将大问题分解为相同或相似的子问题来求解的问题。每一次函数调用自身时,都会尝试缩小问题规模,直到达到一个基本情况(即终止条件),这时不再进行递归调用并直接返回结果。
递归函数的实现通常使用if语句来判断是否达到基本情况,如果达到,则直接返回结果;否则,继续调用函数自身,直到达到基本情况。下面通过例子来进一步解释递归函数:
阶乘
# 计算阶乘定义一个函数a,用于计算n的阶乘。
# 首先判断n是否为0或1,如果是,则直接返回1,这就是基本情况。
# 否则,继续调用函数自身,计算(n-1)的阶乘,就是递归情况。最终得到n的阶乘
def a(n):
# 基本情况
if n == 0 or n == 1:
return 1
# 递归情况
else:
return n * a(n-1)
# 测试
print(a(5)) # 输出 120
该函数采用递归的方式实现阶乘计算。在编程中,递归是指一个函数在其定义内部直接或间接地调用自身的一种编程技术。
对于a(n)函数而言,当其输入值n不为0或1时,函数会返回n * a(n-1)。这意味着要得到n的阶乘(a(n)),我们需要先知道n-1的阶乘(a(n-1))。于是函数开始自上而下地调用自身:
1、当调用a(5)时,它返回5 * a(4)。
2、为了求得这个表达式的值,程序继续执行a(4),返回4 * a(3)。
3、同理,a(3)返回3 * a(2),a(2)返回2 * a(1)。
这个过程持续进行,直到遇到基本情况(即n=1),此时不再进行递归调用,而是直接返回1。
然后,程序按照调用栈的顺序,将之前各个递归步骤中的中间结果逐层合并起来,最终得到n的阶乘值。虽然代码中没有显式使用循环结构,但通过递归机制,实际上实现了从1到n的累乘效果。
斐波那契数列
#斐波那契数列中的每个数都是前两个数的和,
#定义如下:F(0) = 0, F(1) = 1, F(n) = F(n-1) + F(n-2) (对于n > 1)。
def fibonacci(n):
if n <= 0:
return 0
elif n == 1:
return 1
else:
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(10)) # 斐波那契数列第十位,输出: 55
数字之和
计算一个数各个位上的数字之和,也可以使用递归来完成,每次取余数作为当前位数字,并除以10进入下一位。
def sum_1(n):
if n < 10: # 基本情况:单个数字本身
return n
else:
return (n % 10) + sum_1(n // 10)
print(sum_1(18117)) # 输出: 18