px普贤教程PuXian
普贤教程 › 基础教程

函数

函数用 def 定义,参数支持默认值,返回值只有一个——但可以是列表或字典,所以"返回多个值"并不受限。

定义与调用

px
def add(a, b):
    return a + b

print(str(add(1, 2)))
text
3

没有 function、没有 fn(fn 是匿名函数的写法,见后文)、没有类型标注。参数名写错会在运行时得到「未定义变量」。

默认参数

px
def greet(name, greeting = "你好"):
    return greeting + "," + name

print(greet("晨曦"))
print(greet("晨曦", "早上好"))
text
你好,晨曦
早上好,晨曦

有默认值的参数必须放在最后。调用时可以只传前面的参数。

返回值

没有 return 的函数返回 null:

px
def log(msg):
    print("[log] " + msg)

var r = log("hi")
print(str(r == null))
text
[log] hi
true

要返回多个值,用字典或列表:

px
def divmod2(a, b):
    return {"q": a // b, "r": a % b}

var m = divmod2(17, 5)
print(str(m["q"]) + " 余 " + str(m["r"]))
text
3 余 2

递归

px
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

print(str(fib(10)))
text
55
递归深度递归太深会耗尽栈。深层遍历(目录树、嵌套 JSON)优先写成迭代 + 显式栈。
px
# 迭代版:阶乘
def fact(n):
    var r = 1
    var i = 2
    while i <= n:
        r *= i
        i += 1
    return r

print(str(fact(10)))
text
3628800

作用域

规则只有两条,但第二条和很多语言不同,必须记住:

  • 用 var / let 声明,创建局部变量,同名时遮蔽外层;
  • 不声明直接赋值,写入的是已存在的那个外层变量——不会新建局部变量。
px
var counter = 0

def shadow():
    var counter = 1        # 局部变量,外层不受影响
    return counter

def touch():
    counter = 9            # 直接赋值 = 改外层的 counter

print(str(shadow()))
print(str(counter))
touch()
print(str(counter))
text
1
0
9

因此"在函数里加一句赋值"有可能悄悄改掉全局状态。这不是 bug,是语言的选择:声明即局部,赋值即外层。写函数时想清楚要不要改变量。

要跨调用保存状态,用字典或把状态作为参数显式传递:

px
def bump(state):
    state["n"] = state["n"] + 1
    return state["n"]

var st = {"n": 0}
print(str(bump(st)))
print(str(bump(st)))
print(str(st["n"]))
text
1
2
2

函数是一等值

函数可以存在变量里、当参数传:

px
def double(x):
    return x * 2

def apply(f, x):
    return f(x)

print(str(apply(double, 21)))
text
42

匿名函数 fn

短小的函数可以写成 fn(参数): 表达式 形式,常见于回调与字典分派:

px
var ops = {
    "inc": fn(x): x + 1,
    "dec": fn(x): x - 1,
    "sq":  fn(x): x * x
}

def run(name, x):
    if not ops.has(name):
        return Err("未知操作:" + name)
    return Ok(ops[name](x))

print(str(run("sq", 7).ok()))
print(str(run("nope", 1).is_err()))
text
49
true

分派表写法

参数多、分支多时,用字典把「名字 → 处理函数」映射起来,比一长串 elif 好维护:

px
def handle_add(body):
    return Ok(body["a"] + body["b"])

def handle_mul(body):
    return Ok(body["a"] * body["b"])

let ROUTES = {"add": handle_add, "mul": handle_mul}

def dispatch(action, body):
    if not ROUTES.has(action):
        return Err("未知动作:" + action)
    return ROUTES[action](body)

print(str(dispatch("add", {"a": 2, "b": 3}).ok()))
print(str(dispatch("mul", {"a": 2, "b": 3}).ok()))
text
5
6

嵌套函数

px
def outer(x):
    def inner(y):
        return y + 1
    return inner(x) * 2

print(str(outer(3)))
text
8

内层函数能读到外层的变量。这在写小工具时很方便,但别用它来当"隐式全局状态"。

写函数的几条建议

  • 一个函数只做一件事,超过 40 行考虑拆。
  • 参数超过 4 个时,改成传一个字典。
  • 可能失败的函数返回 Ok / Err,不要返回 null 让调用方猜。
  • 不要在函数里改传进来的字典「顺便」当返回值——要么返回,要么显式修改 out 参数,并在注释里写明。