前面三篇(数据类型、条件、循环)讲的是”一段代码怎么处理数据”。这一篇讲怎么把一段重复的逻辑打包成可复用单元——这就是函数。
围绕函数会冒出 4 个最容易混的概念:parameter / argument / return / 全局 vs 局部变量。一次理清。
先看全景:4 个概念对照表
| 概念 | 在哪出现 | 干什么 | 例子(沿用 Google 课的剩余登录次数函数) |
|---|---|---|---|
| parameter 形参 | 函数定义的头部 | 当占位符,等数据进来 | def f(maximum_attempts, total_attempts): 里的两个变量 |
| argument 实参 | 函数调用时 | 真实传进去的数据 | f(3, 2) 里的 3 和 2 |
| return 返回 | 函数体内 | 把结果传出来给调用方 | return maximum_attempts - total_attempts |
| global 全局变量 | 函数外面定义 | 整个程序都能访问 | device_id = "7ad2130bd" |
| local 局部变量 | 函数里面定义 | 函数跑完就消失 | def f(): total_string = "Welcome" 里的 total_string |
一句话区分 parameter 和 argument:定义时叫 parameter(占位),调用时填的实际值叫 argument(实参)。 同一个位置上的东西,在两个时间点上换了名字。
函数的基本骨架
用 Google 课里的例子——算”还剩几次登录机会”:
def remaining_login_attempts(maximum_attempts, total_attempts):
return maximum_attempts - total_attempts拆开看:
def—— 关键字,表示”我要定义一个函数了”- 函数名 ——
remaining_login_attempts,命名规则和变量名一样 (maximum_attempts, total_attempts)—— 括号里是 parameters(形参),函数定义时的占位符:—— 头部结尾必须冒号(和条件、循环一样)- 函数体 —— 缩进的部分,函数被调用时实际跑的代码
调用它:
remaining_login_attempts(3, 2)这里的 3 和 2 就是 arguments(实参)——按位置传给形参:3 → maximum_attempts,2 → total_attempts。函数算出 3 - 2 = 1。
return:把结果传出来
光算出来不够,得把结果传出去给调用方用。这就是 return 的工作。
def remaining_login_attempts(maximum_attempts, total_attempts):
return maximum_attempts - total_attempts
remaining_attempts = remaining_login_attempts(3, 3)
if remaining_attempts <= 0:
print("Your account is locked")- 函数 return 的值,被赋给外面的变量
remaining_attempts - 然后这个变量进入条件判断:剩余 ≤ 0 就锁账号
- 这是真实安全脚本的常见骨架:函数算 → 返回 → 主流程根据返回值决定下一步
两个坑:
return不是函数,后面不加括号 —— 写return(x)能跑,但return x才地道。return一执行,函数就结束 ——return后面的代码不会跑。这在带if分支的函数里特别重要:第一个分支 return 了,后面的分支就不会再走。
全局变量 vs 局部变量
讲完函数的”输入和输出”,再讲它内部的变量。
全局变量 —— 函数外定义,哪都能用
device_id = "7ad2130bd" # 全局变量写在所有函数外面。整段代码里(包括函数内、循环内、条件内)都能访问它。
局部变量 —— 函数内定义,出了函数就没了
def greet_employee(name):
total_string = "Welcome" + name
return total_stringname(参数)和 total_string(函数体内赋值)都是局部变量。函数被调用时它们临时存在,函数返回后立刻被清理掉。
在函数外面用 total_string 会直接报错——因为它出了函数就不存在了。
一条铁律:别让函数偷偷依赖全局变量
看这两段对比:
❌ 不好的写法
username = "elarson"
def identify_user():
print(username) # 函数内部直接读外面的全局变量
identify_user() # 输出: elarson函数能跑,但 username 是从哪来的?读者得跳出函数去看。函数的依赖被藏起来了,日后想让它处理别的用户名,你得改全局变量——这就是大型脚本噩梦的开始。
✅ 好的写法:用参数显式传
def identify_user(username):
print(username)
identify_user("elarson")
identify_user("bmoreno") # 直接换用户名,不用动全局函数需要什么,让调用方通过参数传进去。函数变成一个独立的”黑盒”,只看头部就知道它要啥。
同名变量的陷阱
如果你不小心在函数里用了和全局变量同名的变量,会发生什么?
username = "elarson"
print("1:" + username) # 输出: 1:elarson
def greet():
username = "bmoreno" # 注意:这创建了一个局部变量,和外面的同名但无关
print("2:" + username) # 输出: 2:bmoreno
greet()
print("3:" + username) # 输出: 3:elarson —— 外面的没被改!函数里那行 username = "bmoreno" 没有修改全局 username,而是新建了一个局部变量 username。外面的 elarson 完好无损。
这种”看起来在改其实没改”的 bug 极难排查。所以最佳实践就一条:全局变量和局部变量不要重名,函数也别去碰全局变量,要啥用参数传。
在安全场景里它长什么样
把这些拼起来,一个标准的安全函数就是这样:
def check_login(username, attempts, max_attempts):
"""检查这次登录是不是该被锁"""
if attempts >= max_attempts:
return False # return False → 拒绝
return True # 注意:return 一执行函数就结束,所以这里不需要 else
# 主流程
if not check_login("elarson", attempts=5, max_attempts=3):
print("⚠️ 账号锁定:elarson")注意几个习惯:
- 函数只做一件事(判断要不要锁),不掺其他逻辑
- 需要的数据(用户名、当前次数、上限)全部通过参数传进来,函数里没有任何
username = ...这种硬编码 return让主流程自己决定怎么处置(打印、写日志、发告警),函数本身不做决定
这就是”小函数 + 显式参数 + 用 return 让主流程决策”的写法,无论代码长到多大,都能保持清晰。
一句话总结
parameter 是定义函数时的占位,argument 是调用时塞进去的实际值,return 把结果传出来给外面用。变量分全局(函数外、哪都能用)和局部(函数内、出函数就没了)——铁律:别让函数依赖全局,要什么用参数传。会写函数,你就从”一段段脚本”进化到了”一块块积木”,这是写大型安全自动化的真正起点。