前面三篇(数据类型条件循环)讲的是”一段代码怎么处理数据”。这一篇讲怎么把一段重复的逻辑打包成可复用单元——这就是函数。

围绕函数会冒出 4 个最容易混的概念:parameter / argument / return / 全局 vs 局部变量。一次理清。


先看全景:4 个概念对照表

概念在哪出现干什么例子(沿用 Google 课的剩余登录次数函数)
parameter 形参函数定义的头部当占位符,等数据进来def f(maximum_attempts, total_attempts): 里的两个变量
argument 实参函数调用真实传进去的数据f(3, 2) 里的 32
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)

这里的 32 就是 arguments(实参)——按位置传给形参:3maximum_attempts,2total_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 就锁账号
  • 这是真实安全脚本的常见骨架:函数算 → 返回 → 主流程根据返回值决定下一步

两个坑:

  1. return 不是函数,后面不加括号 —— 写 return(x) 能跑,但 return x 才地道。
  2. return 一执行,函数就结束 —— return 后面的代码不会跑。这在带 if 分支的函数里特别重要:第一个分支 return 了,后面的分支就不会再走。

全局变量 vs 局部变量

讲完函数的”输入和输出”,再讲它内部的变量。

全局变量 —— 函数外定义,哪都能用

device_id = "7ad2130bd"     # 全局变量

写在所有函数外面。整段代码里(包括函数内、循环内、条件内)都能访问它。

局部变量 —— 函数内定义,出了函数就没了

def greet_employee(name):
    total_string = "Welcome" + name
    return total_string

name(参数)和 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 把结果传出来给外面用。变量分全局(函数外、哪都能用)和局部(函数内、出函数就没了)——铁律:别让函数依赖全局,要什么用参数传。会写函数,你就从”一段段脚本”进化到了”一块块积木”,这是写大型安全自动化的真正起点。