day 26
函数的定义:
函数的基本写法如下所示:
```python
def function_name(parameter1, parameter2, ...):
"""
Docstring: 描述函数的功能、参数和返回值 (可选但强烈推荐)
"""
# 函数体: 实现功能的代码
# ...
return value # 可选,用于返回结果
def:关键字,表示开始定义一个函数。
function_name:函数的名称,应遵循python的命名约定(通常是小写字母和多个下划线,例如calculate_area)用英文单词含义和在下划线作为函数名。
- parameter1, parameter2, ...: 函数的参数(也叫形参),是函数在被调用时接收的输入值。参数是可选的。
- (): 参数列表必须放在圆括号中,即使没有参数,括号也不能省略。
- : 冒号表示函数定义的头部结束,接下来是缩进的函数体。
- Docstring (文档字符串): 位于函数定义第一行的多行字符串(通常用三引号 """Docstring goes here""")。用于解释函数的作用、参数、返回值等。可以通过 help(function_name) 或 function_name.__doc__ 查看。这个写法可选,为了后续维护和查看,建议加上这一段更加规范
- 函数体 (Function Body): 缩进的代码块,包含实现函数功能的语句。
- return value: return 语句用于从函数中返回一个值。如果函数没有 return 语句,或者 return 后面没有值,它会自动返回 None。一个函数可以有多个 return 语句(例如在不同的条件分支中)。
不带参数的函数
# 定义一个简单的问候函数
def gree():
message = "Hello, world!"
print(message)
# 调用函数
gree()
# 查看文档字符串,方便查看函数的使用,这个方法可以不掌握
print(gree.__doc__)
# 实际上,当你在py 文件中,鼠标悬停在函数上按住ctrl 即可点击函数跳转到其内部查看函数的定义。
带参数的函数
函数的参数我们有如下的称呼:
parameter 形参:在函数定义中列出的变量名(如name,feature )
arguments (实参):在函调用时传递给函数的实际值(如"张三",10,25)
也就是实际的数值传给了形参,调用的时候把函数的参数称之为实参。
# 定义一个带一个参数的问候函数
def greet_person (name):
"""
这个函数用于向指定的人发送问候消息。
:param name: 要问候的人的姓名
"""
message = f"Hello, {name}!"
print(message)
# 调用函数
greet_person("张三")
# 定义一个带多个参数的函数(例如 在机器学习中计算两个特征的和)
def add_features(feature1, feature2):
"""
计算两个特征的和。
:param feature1: 第一个特征值
:param feature2: 第二个特征值
:return: 两个特征的和
"""
total=feature1 + feature2
print(f"{feature1} + {feature2} = {total}")
add_features(10,25)
带返回值的函数
函数不仅可以执行操作(如打印),还可以计算并返回一个结果。
# 定义一个计算和病返回结果的函数
def calculate_sum(a, b):
"""
计算两个数的和。
:param a: 第一个数
:param b: 第二个数
:return: 两个数的和
"""
total = a + b
return total
print ("hhh")
calculate_sum(10,25)
此时,注意到,print("hhh")这个代码并没有被执行,因为函数在遇到return语句时,就会立即返回,而不会继续执行函数后面的代码。
其次,如果没有return语句,或者return后面不带任何参数,那么函数也会返回None(不要把执行的操作理解为返回值)。
# 函数可以返回多种类型的数据,包括列表、字典等
# 例如,在数据预处理中,一个函数可能返回处理后的特征列表
def preprocess_data(raw_data_points):
"""模型数预处理,例如将所有的数据乘以2
args:
raw_data_points: 原始数据点列表
return:
预处理后的数据点列表
"""
processed=[]
for data_point in raw_data_points:
processed.append(data_point*2)
return processed
data=[1,2,3,4,5]
processed_data=preprocess_data(data)
print(processed_data)
变量作用域
理解变量在何处可见和可访问非常重要
局部变量:在函数内部定义的变量,只在该函数内部有效。当函数执行完毕后,局部变量会被销毁。
全局变量:在所有函数外部定义的变量,可以在程序的任何地方被访问,(但在函数内部修改全局变量需要特殊声明,如global 关键字,初学阶段可以避免)
print("\n -----变量作用域示例----")
global_var = "我是全局变量"
def scope_test():
local_var = "我是局部变量"
print(local_var)
print(global_var)
scope_test()
print (f"\n 在函数的外部,可以看到全局变量: {global_var}")
print (f"\n 但是在函数的外部,不能看到局部变量: {local_var}")
函数的参数类型
在我们ctrl跳转到一些函数内部的时候,会发现写法相对我们日常定义的简单函数更加复杂,主要是参数形式比较丰富
参数有以下类型:
- 位置参数 (Positional Arguments): 调用时按顺序匹配。
- 默认参数值 (Default Parameter Values): 定义函数时给参数指定默认值,调用时如果未提供该参数,则使用默认值。
- 可变数量参数 (*args 和 **kwargs):
- *args: 将多余的位置参数收集为一个元组。
- **kwargs: 将多余的关键字参数收集为一个字典。
可能你还听过关键字参数 (Keyword Arguments)这个说法,但是他并非是一种参数,而是一种传递参数的手段: 调用时通过 参数名=值 的形式指定,可以不按顺序。他可以传位置参数的值,也可以传默认参数的值,也可以传可变参数的值,也可以传关键字参数的值。为了可读性,更推荐对所有参数均采取关键字参数传递。
位置参数
def describe_pet(animal_type, pet_name):
"""显示宠物的信息"""
print(f"\n 我有一只{animal_type},它的名字是{pet_name}")
describe_pet('狗','旺财')
默认参数
注意点:带默认值的参数必须放在没有默认值的参数之后
def describe_pet_default(pet_name,animal_type='狗'):
"""显示宠物的信息"""
print(f"\n 我有一只{animal_type},它的名字是{pet_name}")
describe_pet_default(pet_name="小黑")
describe_pet_default(pet_name="小白",animal_type="猫")
argus (收集位置参数)
argus 将多余的位置参数收集为一个元组.
当函数被调用时,Python 会先尝试用调用时提供的位置参数去填充函数定义中所有明确定义的、非关键字的形参 (也就是那些普通的,没有 * 或 ** 前缀的参数,包括有默认值的和没有默认值的)。
如果在填充完所有这些明确定义的形参后,调用时还有剩余的位置参数,那么这些“多余的”位置参数就会被收集起来,形成一个元组 (tuple),并赋值给 *args 指定的那个变量(通常就是 args)。
如果调用时提供的位置参数数量正好等于或少于明确定义的形参数量(且满足了所有必需参数),那么 *args 就会是一个空元组 ()。
def make_pizza(size,*toppings):
"""概述要制作的比萨。
*toppings 会将所有额外的位路参数收集到一个元组中。
"""
print(f"\n 制作一个{size}寸的比萨,配料如下:")
if toppings:# 只要topping 不为空元组,就执行
for topping in toppings:
print(f" - {topping}")
else:
print(" -原味,(无额外的配料)")
make_pizza(12,"蘑菇")
make_pizza(16,"蘑菇","蘑菇","蘑菇")
make_pizza(9)# toppings 会是空元组
kwargs(收集关键字参数)
kwargs 将多余的关键字参数收集为一个字典。
当函数被调用时,python会线处理完所有位置参数,(包括填充明确定义的形参和收集到*args中)然后python 会看到调用时提供的关键字参数,他尝试用这些关键字参数去填补函数定义中所有与关键字同名的、明确定义的形参(这些形参可能之前没有被位置参数填充)
如果在填充完所有通过名字匹配上的明确定义的形参后,调用时还有剩余的关键参数。
那么这些多余的关键字参数就会被收集起来,形成一个字典,赋值给kwargs指定的那个变量。
如果调用时提供的所有关键字参数都能在函数中找到对应的明确的形参名,那么kwargs就会时一个空字典{}
def build_profile(first_name,last_name,**user_info):
"""创建一个字典,其中包含我们知道的有关用户的一切"""
profile={}
profile['first_name']=first_name
profile['last_name']=last_name
for key,value in user_info.items():
profile[key]=value
return profile
user_profile=build_profile('gj','li',location='北京',field='python')
print(user_profile)
*args 和 **kwargs 的核心目的是让函数能够接收不定数量的参数,并以元组和字典的形式在函数内部进行处理。
也就是说 当位置参数用完了 就自动变成*args,当关键词参数用完了 就自动变成**kwarges
# 同时出现*args和**kwargs,注意参数的传入顺序
def processed_data(id_num,name,*tags,status="pending",**details):
print(f"ID: {id_num}")
print(f"Name: {name}")
print(f"Tags (*args): {tags}")
print(f"Status: {status}") # status 是一个有默认值的普通关键字参数
print(f"Details (**kwargs): {details}")
print("-" * 20)
# 调用1:
processed_data(101,"Alice","vip","new_user",location="usa",age=30)
# ID: 101
# Name: Alice
# Tags (*args): ('vip', 'new_user') <-- "vip", "new_user" 是多余的位置参数,被 *tags 收集
# Status: pending <-- status 使用默认值,因为调用中没有 status=...
# Details (**kwargs): {'location': 'USA', 'age': 30} <-- location 和 age 是多余的关键字参数,被 **details 收集
#调用2:
processed_data(102,"Bob",status="active",department="sales")
# ID: 102
# Name: Bob
# Tags (*args): () <-- 没有多余的位置参数
# Status: active <-- status 被关键字参数 'active' 覆盖
# Details (**kwargs): {'department': 'Sales'} <-- department 是多余的关键字参数
# --------------------
# 调用3:
processed_data(103,"Charlie","admin")# admin 会被* tags捕获
# ID: 103
# Name: Charlie
# Tags (*args): ('admin',)
# Status: pending
# Details (**kwargs): {}
# --------------------
# 调用4:(演示关键字参数也可以用于定义中的位置参数)
processed_data(name="David",id_num=104,profession="Engineer")
# ID: 104
# Name: David
# Tags (*args): ()
# Status: pending
# Details (**kwargs): {'profession': 'Engineer'}
# --------------------
@ 浙大疏锦行