在Python中,函数是一种组织好的、可重复使用的、用来实现单一或相关联功能的代码段。下面是对您提到的几个关于Python函数的关键点的详细解释:
自定义函数
自定义函数允许你根据需要创建自己的功能模块。基本语法如下:
def function_name(parameters):
"""函数文档字符串(可选)"""
# 函数体
return result # 返回值,可选
含参与不含参函数
- 不含参函数:如果函数不需要任何外部输入,可以不定义参数。
def greet():
print("Hello!")
greet() # 输出: Hello!
- 含参函数:当函数需要处理外部数据时,就需要定义参数。
def add_numbers(a, b):
return a + b
sum = add_numbers(3, 4) # sum = 7
- 实参和形参:
#实参和型参
def get_mux(a,b):
# 其中a,b为形参
if a>b:
print(a,"是较大的值")
else:
print(b,"是较小的值")
get_mux(6,5) #6,5是实参 实参传值给形参 也就是 a=6, b=5,形参=实参
其中a,b为形参;6,5是实参 实参传值给形参 也就是 a=6, b=5,形参=实参
参数(同样包括实参和形参)
- 位置参数:调用函数时,按照函数定义时的顺序传递参数。
def say_hello(name, age):
print(f"Hello, {name}, you are {age} years old.")
say_hello("Alice", 30) # 正确,按位置传递
- 关键字参数:调用时明确指定参数名和对应的值,不必保持定义时的顺序。
say_hello(age=30, name="Bob") # 正确,使用关键字参数
- 实例:
#实参和型参
def get_mux(a,b):
# 其中a,b为形参
if a>b:
print(a,"是较大的值")
else:
print(b,"是较小的值")
get_mux(6,5) #6,5是实参 实参传值给形参 也就是 a=6, b=5,形参=实参
def get_mux(a,b):
# 其中a,b为形参
if a>b:
print(a,"是较大的值")
else:
print(b,"是较小的值")
#使用关键字传参
get_mux(b=5,a=6)
在python3.8中新增了仅限位置传参的语法,使用"/"来限定部分形参只接受采用位置参数传递的实参代码如下
#在python3.8中新增了仅限位置传参的语法,使用"/"来限定部分形参只接受采用位置参数传递的实参
#代码如下
#/ 指明前面的参数a,b均为位置形参
def func(a,b,/,c):
print(a,b,c)
#演示两种错误的调用方式
# func(a=10,20,30)
# 报错 File "E:\test1\test1\函数.py", line 54
# func(a=10,20,30)
# ^
# SyntaxError: positional argument follows keyword argument
# func(10,b=20,30)
# 报错:File "E:\test1\test1\函数.py", line 59
# func(10,b=20,30)
# ^
# SyntaxError: positional argument follows keyword argument
#正确的调用方式
#c可以为关键字传参也可为位置传参 a,b必须为位置传参
func(10,20,c=60) #输出10 20 60
func(10,20,60) #输出10 20 60
解包与打包参数
-
解包:当你有一个序列(如列表或元组)或字典,希望将它们的元素作为单独的参数传递给函数时,可以使用星号
*
(对于序列)和双星号**
(对于字典)来解包。def print_args(a, b, c): print(a, b, c) args = (1, 2, 3) kwargs = {'x': 1, 'y': 2, 'z': 3} print_args(*args) # 输出: 1 2 3 print_args(**kwargs) # 输出: x=1 y=2 z=3 注意这里函数签名需要匹配关键字参数
-
打包通常指的是将多个值打包成一个序列或字典。在函数调用上下文中,解包更常见。
在Python中,混合传递参数时需要遵循以下规则和优先级:
- 位置参数 必须首先出现,并且严格按照函数定义的顺序。
- 默认参数 可以省略,从左至右第一个未被位置参数或关键字参数赋值的参数开始自动匹配。
- 关键字参数 可以出现在任何位置,不依赖于位置,只要参数名与函数定义匹配即可。
- 可变参数(*args 和 **kwargs)总是位于最后,用于收集额外的位置参数或关键字参数。
示例
假设我们有这样一个函数定义:
def example_function(a, b=0, *args, c='default_c', d=None, **kwargs):
print("a =", a)
print("b =", b)
print("args =", args)
print("c =", c)
print("d =", d)
print("kwargs =", kwargs)
这个函数展示了位置参数、默认参数、可变位置参数、关键字参数以及可变关键字参数的混合使用。
调用示例及解析
- 仅使用位置参数和默认参数
example_function(1, 2)
a
被赋值为 1(位置参数)b
被赋值为 2(位置参数)- 其他默认参数保持不变
- 混合位置参数、关键字参数和默认参数
example_function(1, d="custom_d", c="override_default")
a
为 1(位置参数)b
保留默认值 0(未提供,使用默认)c
被override_default
关键字参数覆盖d
被设置为 "custom_d"(关键字参数)args
和kwargs
为空,因为没有额外的参数
- 使用可变参数和关键字参数
example_function(1, 2, 3, 4, e="extra_kwarg", f="another_extra_kwarg", c="from_kwarg")
a
为 1,b
为 2(位置参数)args
收集了额外的位置参数 [3, 4]c
通过关键字参数覆盖为 "from_kwarg"d
保持默认值 Nonekwargs
收集了额外的关键字参数 {'e': 'extra_kwarg', 'f': 'another_extra_kwarg'}
综合实训建议
- 练习编写包含各种参数类型的函数:尝试编写包含位置参数、默认参数、可变参数、关键字参数的函数,理解它们如何工作。
- 混合调用函数:尝试以不同的方式调用上述函数,包括只使用位置参数、混合使用位置和关键字参数、使用默认参数、以及利用可变参数收集额外的输入。
- 错误处理:故意构造一些违反参数传递规则的调用,观察Python是如何报错的,这有助于深入理解参数传递机制。
- 阅读他人代码:查看开源项目或同事的代码中函数定义和调用的方式,分析他们如何有效地使用混合参数传递来提高代码的可读性和灵活性。
不定长参数
*args
:用于收集额外的非关键字参数,作为元组传递。**kwargs
:用于收集额外的关键字参数,作为字典传递。
def example_function(a, *args, **kwargs):
print(f"a: {a}")
print(f"args: {args}")
print(f"kwargs: {kwargs}")
example_function(1, 2, 3, key1="value1", key2="value2")
# 输出:
# a: 1
# args: (2, 3)
# kwargs: {'key1': 'value1', 'key2': 'value2'}
返回值
函数可以通过 return
语句返回一个或多个值。如果没有 return
语句,函数默认返回 None
。
def add_and_multiply(x, y):
return x + y, x * y
sum, product = add_and_multiply(4, 5)
print(sum, product) # 输出: 9 20
以上概述了Python函数的一些核心概念,包括如何定义含参或不含参的函数,如何使用位置参数与关键字参数,如何解包序列和字典作为函数参数,以及如何处理不定长参数和返回值。
默认值参数是Python函数定义中的一个强大特性,它允许你在定义函数时为参数指定一个默认值。如果在调用函数时没有为这样的参数提供值,那么就会使用定义时设置的默认值。这增加了函数的灵活性和可用性。
基本用法
在参数列表中,你可以为任何参数指定默认值,只需在参数名后面紧跟等号 =
和默认值即可。注意,默认值参数应该放在参数列表的末尾,否则会导致语法错误或意料之外的行为。
def greet(name, message="Hello"):
"""向某人打招呼,如果没有提供消息,则使用默认消息。"""
print(f"{message}, {name}!")
greet("Alice") # 使用默认消息: Hello, Alice!
greet("Bob", "Hi there") # 覆盖默认消息: Hi there, Bob!
特别注意
- 当有默认值的参数和无默认值的参数混用时,无默认值的参数必须放在前面。
- 如果函数有多个默认值参数,可以自由选择为其中的某些指定值,而其余的使用默认值。
- 默认值参数在函数定义时计算并存储,因此如果默认值是可变对象(如列表、字典),修改这个默认值会影响到后续的所有函数调用。
示例:避免默认值的陷阱
对于可变默认参数,要注意的是默认值只在函数定义时初始化一次,多次调用函数且在调用中修改默认值参数时,会影响其原始值。
def append_item(item, lst=[]):
lst.append(item)
return lst
print(append_item(1)) # 输出: [1]
print(append_item(2)) # 输出: [1, 2] 而不是预期的[2],因为lst是默认值,被保留了
为了避免这个问题,可以将默认值设为 None
,然后在函数内部初始化可变对象。
def better_append_item(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
print(better_append_item(1)) # 输出: [1]
print(better_append_item(2)) # 输出: [2],每次调用都是新的列表
通过这种方式,你可以有效地利用默认值参数,同时避免潜在的副作用。
解包和打包是Python中处理函数参数的两种常用技术,让我们通过实例来理解这两个概念以及如何混合使用它们。
注意,虽然函数中添加""或者“**”的形参可以是符合命名规范的任意名称,但一般建议使用args和kwargs。若函数没有接收到任何数据参数*args和kwargs为空,即他们为空元组或空字典
解包实例
解包是指将容器类型(如列表、元组或字典)的元素展开为单独的参数传递给函数。这主要通过在参数前使用星号 *
(针对序列)或双星号 **
(针对字典)来实现。
序列解包实例
假设有一个函数接收两个参数:
def add_numbers(a, b):
return a + b
你可以使用元组或列表解包来调用它:
numbers = (3, 4)
result = add_numbers(*numbers) # 相当于 add_numbers(3, 4)
print(result) # 输出: 7
字典解包实例
对于关键字参数,可以使用字典解包:
def introduce(name, age):
return f"My name is {name} and I am {age} years old."
person_info = {"name": "Alice", "age": 30}
introduction = introduce(**person_info) # 相当于 introduce(name="Alice", age=30)
print(introduction) # 输出: My name is Alice and I am 30 years old.
打包实例
打包是指将多个参数收集起来放入一个容器(通常是元组或字典)。虽然这个术语不如解包常用,但我们可以认为在使用 *args
和 **kwargs
定义函数时,是在准备“打包”未知数量的参数。
位置参数打包
def print_values(*args):
for value in args:
print(value)
values = [1, 2, 3]
print_values(*values) # 输出: 1 2 3
关键字参数打包
def print_details(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
details = {"name": "Bob", "age": 25}
print_details(**details) # 输出: name: Bob age: 25
混合传递实例
你可以同时使用位置参数、关键字参数、解包和打包来调用函数,只要遵循正确的语法顺序。
def complex_example(a, b, *args, c=None, **kwargs):
print(f"a: {a}, b: {b}, c: {c}")
print("Additional args:", args)
print("Keyword args:", kwargs)
params = [1, 2]
extras = [3, 4]
kwparams = {"d": 5, "e": 6}
complex_example(*params, *extras, c=7, **kwparams)
# 输出可能类似于:
# a: 1, b: 2, c: 7
# Additional args: (3, 4)
# Keyword args: {'d': 5, 'e': 6}
在这个例子中,我们首先使用 *params
解包元组为 a
和 b
,然后用 *extras
进一步添加位置参数,直接指定了 c
的值,并通过 **kwparams
传递了额外的关键字参数。
在 Python 中,函数参数的传递有一些基本原则,特别是在混合传递参数时。让我们来看看这些规则:
- 先按照参数的位置传递:首先,函数会按照参数在定义时的位置顺序进行传递。也就是说,实参会严格按照形参的位置进行匹配。
- 再按照参数的关键字传递:其次,Python 支持关键字参数,你可以根据参数的名称来传递实参。这样,你可以忽略参数的位置关系,直接根据关键字来赋值。关键字参数的好处之一是,你可以在调用函数时将某些参数作为选填项,不需要严格匹配数量。
- 最后按包裹的形式传递:包裹形式包括两种:
*args
:这种方式可以传入任意数量的位置参数,这些参数会被放到一个元组中,然后赋值给行参args
。在函数内部,你可以直接操作这个元组。**kwargs
:这是最灵活的方式,以键值对字典的形式传递参数。它既具有关键字传递的灵活性,又没有数量上的限制。
需要注意的是,如果定义函数时参数有默认值,那么带有默认值的参数必须跟在必选参数的后面。此外,混合使用这些传参方式时,要遵守一定的顺序规则。
示例
# 示例
def my_function(a, b, c=0, *args, **kwargs):
print(f"a={a}, b={b}, c={c}, args={args}, kwargs={kwargs}")
my_function(1, 2) # 按位置传递方式将 1、2 赋值给 a、b,c 采用默认值 0
my_function(1, 2, c=3) # 按位置传递方式将 1、2 赋值给 a、b,将 3 赋值给 c
my_function(1, 2, 3, 'a', 'b') # a=1, b=2, c=3, args=('a', 'b'), kwargs={}
my_function(1, 2, 3, 'a', 'b', x=99) # a=1, b=2, c=3, args=('a', 'b'), kwargs={'x': 99}
实例
#date 2024/5/15
#定义函数
def test110():
print("老铁666")
return 1
test110()
if test110():
print("666")
#传参的函数
def add111(one,two):
return one+two
#调用函数
a=add111(6,4)
print (a)
#函数的嵌套定义
def add2525(one,two):
def add222():
print(add222())
return 1+5
return one+two
#可见并不会输出内部函数的值 结果为one+two的值 这里是12
print(add2525(7,5))
#在外部无法调用在函数内部定义的函数
# add222() 报错NameError: name 'add222' is not defined
#只能在函数中调用
def add2525(one,two):
def add222():
print(add222())
return 1+5
#调用内部的add222()
print(add222())
return one+two
#实参和型参
def get_mux(a,b):
# 其中a,b为形参
if a>b:
print(a,"是较大的值")
else:
print(b,"是较小的值")
get_mux(6,5) #6,5是实参 实参传值给形参 也就是 a=6, b=5,形参=实参
def get_mux(a,b):
# 其中a,b为形参
if a>b:
print(a,"是较大的值")
else:
print(b,"是较小的值")
#使用关键字传参
get_mux(b=5,a=6)
#在python3.8中新增了仅限位置传参的语法,使用"/"来限定部分形参只接受采用位置参数传递的实参
#代码如下
#/ 指明前面的参数a,b均为位置形参
def func(a,b,/,c):
print(a,b,c)
#演示两种错误的调用方式
# func(a=10,20,30)
# 报错 File "E:\test1\test1\函数.py", line 54
# func(a=10,20,30)
# ^
# SyntaxError: positional argument follows keyword argument
# func(10,b=20,30)
# 报错:File "E:\test1\test1\函数.py", line 59
# func(10,b=20,30)
# ^
# SyntaxError: positional argument follows keyword argument
#正确的调用方式
#c可以为关键字传参也可为位置传参 a,b必须为位置传参
func(10,20,c=60) #输出10 20 60
func(10,20,60) #输出10 20 60
#默认值参数的传递
def connect(ip,port=8080):
print(f'{ip}:{port}')
#使用示例
#不填写port
connect('127.0.0.1')
connect(ip='127.0.0.1')
#填写port使用位置参数
connect('127.0.0.1',8090)
#填写port使用关键字参数
connect(port='9080',ip='127.0.0.1')
# 输出结果
# 127.0.0.1:8080
# 127.0.0.1:8080
# 127.0.0.1:8090
# 127.0.0.1:9080
#参数的打包和解包
#打包 如果无法在定义时确定需要接受多少个数据,那么可以在定义函数时为形参添加"*"或者"**";
#如果形参前面加上"*",那么他可以接受以元组形式打包的多个值;
#如果在形参前面加上"**",那么它可以接受以字典形式打包的多个值;
#定义一个形参为*arg的函数test()
def test888(*args):
print(args)
#调用test()函数时传入多个实参,多个实参会在打包后传递给形参
test888(11,22,33,44,55) #输出 (11, 22, 33, 44, 55)
#由上述运行结果可知,python解释器将传给test()函数的所有值打包成元组后传递给了形参*args
#定义一个形参为**kwargs的函数test(),
def test999(**kwargs):
print(kwargs)
#调用test()函数时传入多个绑定关键字的实参
test999(a=1,b=2,c=3) #输出 {'a': 1, 'b': 2, 'c': 3}
'''
由以上运行结果可知,python解释器将传给test()函数的所有值打包成字典后传递给了形参**args
注意,虽然函数中添加"*"或者“**”的形参可以是符合命名规范的任意名称,但一般建议使用*args和**kwargs。
若函数没有接收到任何数据参数*args和**kwargs为空,即他们为空元组或空字典
'''
#解包
test=(11,22,33,44,55)
def test11(a,b,c,d,e):
print (test)
test11(*test)
test={'a':1,'b':6,'c':5,'d':6,'e':7}
test11(**test)
'''
函数的返回值函数返回值后会直接退出不再执行
'''
def test12(num):
return num
print ('123不执行错误写法不要模仿')
print (test12(22))
'''
函数返回多个值
'''
def move(x,y,step):
nx=x+step
ny=y-step
#这样返回的值会被保存在元组中
return nx,ny
print (move(1,2,3))
'''
变量的作用域
'''
#局部变量
'''
局部变量只能在函数内部被调用,执行结束后局部变量会被释放无法访问
if while for 循环 自定义函数中创建的变量都是局部变量
'''
def test12():
num=10 #这是局部变量
print(num)
#print (num) #不可访问
'''
print (num)
报错信息
Traceback (most recent call last):
File "\test1\test1\函数.py", line 147, in <module>
print (num) #不可访问
NameError: name 'num' is not defined
'''
'''
不同函数间的局部变量互不影响,互不互通
'''
def test13():
number=1
print(number)
def test14():
number=2
print(number)
test13()
test14()
'''
全局变量 只能在内部被访问无法修改,可以通过global关键字修改
'''
number = 1
def test15():
number=15
print(number)
test15()
print (number)
#global 和 nonlocal关键字
def test16():
global number #用global关键字声明number
number =15
print(number)
test16()
print (number)
#nonlocal关键字 可以在局部作用域中修改嵌套作用域中声明的变量
def test17():
number=10
def test_18():
nonlocal number
number = 6
test_18()
print (number)
test17()
#特殊形式的函数
#递归函数 若函数内部调用了自身,着这个函数会被称为递归函数
'''
def 函数名([参数列表]):
if 边界条件:
return 结果
else:
return 递归公式
(1)递推:队规本次的执行都基于上一次运算的结果
(2)回溯:遇到终止条件是,则沿着递推一级一级的吧值返回来
'''
#实列 阶乘
def func(num):
if num ==1:
return 1
else:
return num*func(num-1)
print (func (2))
#匿名函数 使用 lambda 定义 lambda <形式参数列表>:<表达式>
temp = lambda a,b,c:print(a,b,c)
temp(1,2,3)