专业编程基础技术教程

网站首页 > 基础教程 正文

Python 函数全解+闭坑指南,一篇就够

ccvgpt 2025-03-18 20:08:55 基础教程 1 ℃

引言

学完了python的基础数据类型,也算入门了,接下来就是python中最重要的概念之一:函数(function)。大家都知道python是面向对象的编程语言,语法简单,上手容易,关键是新人也不用太纠结面向对象这个概念,仅仅会用函数也可以帮助大家解决很多的问题,接下来我们就开启函数介绍之旅。
在 Python 编程世界里,函数可是极为关键的存在。今天,就让我们跟随小白和专家的对话,一起深入了解 Python 函数的奥秘。

小白(抬头仰望):专家专家,我知道 Python 里函数很重要,可函数到底是啥呀?
专家(低头沉思):简单来说,函数就是把一段代码封装起来,方便复用。你可以把它想象成一个神奇的小盒子,输入一些东西,经过处理后输出结果。比如我们常见的print()函数,输入要打印的内容,它就会在屏幕上输出。

Python 函数全解+闭坑指南,一篇就够

小白(略显骄傲):经过学习,我写了一个简单的函数,实现了读取txt文件内容的功能,省了很多事呢。

#读取文件内容,循环打印每行的内容
def opentxt(file):
    f = open(file,'r',encoding='utf8')
    content = f.readlines()
    for i  in content:
  	    print(i)

专家(竖起大拇指):嗯,别小看一个函数,能解决大问题。

函数基础四重奏

1. 函数的定义

# 标准创建格式
def 函数名称(参数):
  函数体
  return 返回值 or(yield 返回值)
  • 参数:参数为非必须;如果需要传参,参数可以有多种类型:默认参数,位置参数,关键字参数,不定长参数等
  • return语句可不添加,默认返回None; 执行到return语句,函数返回并结束执行。
  • yield语句定义之后函数为生成器,可以参考之前文章python生成器介绍

2. 函数的调用

# 正确调用
函数名()  #调用必须加括号,括号填参数或者不填参数

# 错误调用
函数名   #不加括号是无法调用的

3. 函数参数介绍

函数配置了定长参数(有明确的参数个数)

  • 函数执行时必须传参,否则报错
#函数带一个参数
def sum_num(numlist):
  print(f'值汇总结果:{sum(numlist)}')

#不传参数 执行函数会报错
sum_num()

#报错如下
Traceback (most recent call last):
File "/est.py", line 6, in 
sum_num()
TypeError: sum_num() missing 1 required positional argument: 'numlist'
  • 参数传多了会提示多传了参数
sum_num(1,2)

#报错如下
Traceback (most recent call last):
File "/test.py", line 5, in 
sum_num(1,2)
TypeError: sum_num() takes 1 positional argument but 2 were given
  • 位置参数:指函数在传递参数时有明确的前后位置关系,如果顺序错误可能会导致程序异常,所以大家写代码时一定要注意。
#函数有两个参数a和b
def division_twonum(a,b):
  print(f'a/b:{a/b}')

#执行以下函数 传参0和5 执行pass
division_twonum(0,5)

#如果a和b搞错了顺序,会报错
division_twonum(5,0)
报错如下:分母不能为0
Traceback (most recent call last):
File "/test.py", line 13, in 
division_twonum(5,0)
File "/test.py", line 10, in division_twonum
print(f'a/b:{a/b}')
ZeroDivisionError: division by zero
  • 关键字参数:传入参数为key=value的形式,不关心前后位置关系。

比如上面的例子,执行以下函数,结果都是一样的。函数不关心a和b的位置。

division_twonum(a=0,b=5)
division_twonum(b=5,a=0)
  • 默认值参数:在函数定义时指定默认值,指定默认值的参数可传可不传,不传递直接使用默认值。
def division_twonum(a,b=2):
  print(f'a/b:{a/b}')

#参数b不传递 使用默认值
division_twonum(5)

打印结果
a/b:2.5

函数的不定长参数

  • 参数定义包含*args,表示可以传入多个位置参数,被args接收为元组类型。
def sum_num(*nums):
  print(f'nums : {nums}, type(nums)为{type(nums)}')
  print(f'值汇总结果:{sum(nums)}')

#传入1到5 数字 执行函数
sum_num(1,2,3,4,5)

结果如下
nums : (1, 2, 3, 4, 5), type(nums)为
值汇总结果:15
  • 参数定义包含**args,表示可以传入多个key=value 的关键字参数,被args接收为字典类型。
def sum_num(**nums):
  print(f'nums : {nums}, type(nums)为{type(nums)}')
  print(f'值汇总结果:{sum(nums.values())}')

#传入key=value的关键字参数 执行函数
sum_num(a=1,b=2,c=3)

结果如下
nums : {'a': 1, 'b': 2, 'c': 3}, type(nums)为
值汇总结果:6
  • *args和 **args一起使用时,*args要放在前面。
def sum_num(*nums,**keys):
  print(f'nums : {nums}, type(nums)为{type(nums)}')
  print(f'keys : {keys}, type(keys)为{type(keys)}')

#传入key=value的关键字参数 执行函数
sum_num(1,2,3,4,5,a=1,b=2,c=3)

#结果
nums : (1, 2, 3, 4, 5), type(nums)为
keys : {'a': 1, 'b': 2, 'c': 3}, type(keys)为

专家提醒:使用关键字参数,能打乱参数顺序,使代码更易读。函数定义顺序:位置参数 → 默认参数 → 可变参数!

函数参数使用*分割

函数参数中使用*分割,表示*前面的参数为位置参数,后面的参数为关键字参数。

#举例 定义函数 有a,b,c,d4个参数,*位于中间
def printnum(a,b,*,c,d):
  print(f'a,b : {a} {b}')
  print(f'c,d : {c} {d}')

传入如下参数执行:

printnum(1,2,c=3,d=4)
#结果:
a,b : 1 2
c,d : 3 4

printnum(1,2,d=4,c=3)
#结果: 跟上面结果一样
a,b : 1 2
c,d : 3 4

如果传入如下参数执行会报错:

printnum(1,2,3,4)
#结果
Traceback (most recent call last):
File "/test.py", line 25, in 
printnum(1,2,3,4)
TypeError: printnum() takes 2 positional arguments but 4 were given


4. 返回值与变量作用域

返回多个值

def analyze_data(data):
	return min(data), max(data), len(data)

low, high, len = analyze_data([1,2,3])
print(f"最低:{low}, 最高:{high}, 长度:{len}")


Python 函数可返回多个值,本质是返回一个元组,通过解包能轻松获取各个值。

5 变量作用域

total = 0  # 全局变量
def add_coffee():
 global total
 count = 1 # 局部变量
 total += count

add_coffee()
print(total) # → 1
# print(count)  局部变量外部不可访问
  • 函数外部的是变量是全局变量,函数内部是局部变量(只能在函数内部使用)
  • 函数内可以使用全局变量,但是如果想修改全局变量需要添加global声明,如上面的global total

6 闭包函数

闭包函数是函数内部嵌套其他的函数。

def power_factory(exponent):
    def power(base):
        return base ** exponent
    return power

square = power_factory(2) #返回的是内部函数power 
print(square(5)) # → 25  执行内部函数 参数base=5


7 函数传参是值传递还是引用传递?

既不是值传递也不是引用传递,其实函数传递的是参数所指向的对象。

我们以参数为列表和字符串举例。列表为可变数据类型,字符串为不可变数据类型。

  • 函数参数传入列表

listA = [1,4,3,2]
#打印列表的指向的对象的唯一内存地址
print(f'listA内存地址:{id(listA)}')

#定义一个函数 设置的形参需要传递一个列表
def sum_num(numlist):
  #打印函数的参数传递后对应的内存地址
  print(f'numlist内存地址:{id(numlist)}')
  #对列表修改操作
  numlist.append(5)
  #打印修改后的列表
  print(f'numlist:{numlist}')

#将列表listA传递给函数并执行函数
sum_num(listA)

#函数执行后打印列表listA
print(f'listA:{listA}')

函数执行结果:

listA内存地址:4504899072
numlist内存地址:4504899072
numlist:[1, 4, 3, 2, 5]
listA:[1, 4, 3, 2, 5]

结果显示函数外定义的列表和函数内传递的参数对应是同一个内存地址,当在函数内对参数进行修改时,因为列表是可变类型,是支持修改的,其实修改的是他们共同指向的对象,所以修改后打印的两个列表值是相同的。

  • 函数参数传入字符串
#定义一个字符串
strA = 'hello python'
print(f'strA内存地址:{id(strA)}')

def printstr(str_a):
  print(f'str_a内存地址:{id(str_a)}') 
  #修改字符串
  str_a += '.'
  print(f'str_a:{str_a}')
  print(f'str_a 内存地址:{id(str_a)}')

#执行函数
printstr(strA)
#打印原变量信息
print(f'strA:{strA}')
print(f'strA 内存地址:{id(strA)}')

函数执行结果如下

strA内存地址:4504812144
str_a内存地址:4504812144
str_a:hello python.
str_a 内存地址:4504903536
strA:hello python
strA 内存地址:4504812144

结果显示函数外定义的字符串和函数内传递的参数对应是同一个内存地址,当在函数内对参数进行修改时,因为字符串是不可变的,不支持修改,所以函数内被修改后的参数指向了另一个内存地址,而函数外定义的字符串还是指向原来的对象。

警惕陷阱

可变默认参数

def add_item(item, lst=[]):  # 
	lst.append(item)
	return lst

# 正确做法:使用None
def add_item(item, lst=None):
	lst = lst or []
	lst.append(item)
	return lst


可变对象作默认参数,会在函数定义时创建并复用,易出意外结果。用None作默认值,在函数内按需创建可变对象更安全。

全局变量污染

total = 0
def calculate():
 total = 100 # 创建局部变量
 # 正确做法:使用global声明

在函数内未声明global,直接给全局变量赋值,会创建同名局部变量,导致逻辑错误。修改全局变量需用global声明。

专家终极指南

  • 参数传递是 "传对象引用":理解这一点,能避免参数传递时的误解。
  • 返回多个值实际是返回元组:清楚其本质,有助于更好地处理返回值。
  • 全局变量要慎用:过多使用易造成代码混乱,应尽量减少其使用。
  • 闭包记得用 nonlocal:在闭包中修改外部变量,nonlocal必不可少。

小白:(跪了)原来函数有这么多学问!

专家:(扶起小白)记住:好的函数就像瑞士军刀 —— 功能明确,安全可靠!

最近发表
标签列表