def 关键字用于定义函数。 它后面依次是函数名、参数列表, 以及一个用于开始函数体的冒号:

# a_function.py

def a_function(response):
    val = 0
    if response == "yes":
        print("affirmative")
        val = 1
    print("continuing...")
    return val

print(a_function("no"))
#: continuing...
#: 0
print(a_function("yes"))
#: affirmative
#: continuing...
#: 1

这里的函数 signature 只指定了函数名和参数名, 没有指定参数类型或返回类型 (静态类型会介绍这些内容)。 Python 是动态类型语言, 所以类型错误会在运行时而不是编译时暴露出来。 因此,同一个函数可以接受和返回不同类型:

# flexible_args_and_returns.py

def flexible_args_and_returns(arg):
    if arg == 1:
        return "Hello"
    if arg == "one":
        return 2

print(flexible_args_and_returns(1))
#: Hello
print(flexible_args_and_returns("one"))
#: 2
print(flexible_args_and_returns(2))
#: None

每一个 Python 函数都会返回一个值。 第三次调用没有匹配任何测试条件, 因此函数执行到结尾而没有返回任何东西,最终产生 None。 裸写 return 和完全没有 return 都会产生 None。

一个带多个表达式的 return 会产生 tuple, 调用方通常会把它解包:

def minmax(values):
    return min(values), max(values)

low, high = minmax([3, 1, 4])

这些逗号构造了 tuple。 函数本身仍然只返回一个对象。

这里,同一个函数把 + 运算符同时应用于整数和字符串:

# add.py

def add(arg1, arg2):
    return arg1 + arg2

print(add(42, 47))
#: 89
print(add("spam ", "eggs"))
#: spam eggs
try:
    add(42, "spam")
except TypeError as e:
    print(e)
#: unsupported operand type(s) for +: 'int' and 'str'

只要函数能够把自己的操作应用到某个参数上,这个参数就可以工作。 失败发生在调用内部执行 + 的地方,而不是函数调用动作本身。 参数进入函数时,没有任何东西会预先检查它们。

默认参数与关键字参数

参数可以具有默认值, keyword argument 允许调用者按名称、以任意顺序传入它们, 从而让函数调用具有自解释性:

# default_args.py

def connect(host, port=5432, timeout=30):
    return f"{host}:{port} (timeout{timeout}s)"

print(connect("db.example.com"))  # Uses both defaults
#: db.example.com:5432 (timeout 30s)
# Skip to a keyword
print(connect("db.example.com", timeout=5))
#: db.example.com:5432 (timeout 5s)
# Any order by name
print(connect(port=80, host="web.example.com"))
#: web.example.com:80 (timeout 30s)

按名称传参并不要求这个参数必须有默认值:host 没有默认值, 但最后一次调用仍然按名称传入了它。 在调用位置,所有 keyword argument 都必须位于 positional argument 之后。 connect(port=80, "web.example.com") 是 SyntaxError: positional argument follows keyword argument。

带默认值的参数不能位于没有默认值的参数之前。 def f(a=1, b): 是 SyntaxError: parameter without a default follows parameter with a default。 keyword-only 参数 不受这一限制,因为调用者本来就必须显式写出它们的名称。

Python 会在函数定义时只计算一次默认值。 因此,所有调用会共享同一个可变默认值:

# mutable_default.py

def bad_append(item, target=[]):  # The same list every call
    target.append(item)
    return target

print(bad_append(1))
#: [1]
print(bad_append(2))  # Surprise, the default kept the 1
#: [1, 2]
print(bad_append.__defaults__)
#: ([1, 2],)

def good_append(item, target=None):
    if target is None:
        target = []  # A fresh list each call
    target.append(item)
    return target

print(good_append(1))
#: [1]
print(good_append(2))
#: [2]