第 04 章
函数、参数与作用域
长视频第四章:用 def 定义可复用逻辑,讲清位置参数与关键字参数、默认值、局部与全局变量,以及 return 与 print 的本质区别。
Functions, Parameters and Scope
Chapter 4 of the long-form course: define reusable logic with def, work with positional and keyword parameters and defaults, understand local versus global scope, and see why return differs from print.
篇内目录
- What this chapter covers
- Timeline
- From writing once to defining once
- Anatomy
- Parameters: giving functions input
- Positional parameters
- Keyword arguments
- Default parameters
- A realistic example
- Local and global variables
- return: handing the result back
- Composing with return values
- Returning several values
- From functions to modules
- Takeaways
- Check your understanding
- How it connects
这一章在讲什么
这是长视频的第四章,对应 2:06:10–2:49:11。前三章把零件备齐了,这一章开始组装:把一段逻辑命名、封装、重复调用。内容包括函数的定义与调用、参数(位置、关键字、默认值)、局部变量与全局变量的区别、返回值,最后引出模块与 import——Python 生态真正的入口。
作者反复强调一个区分,它决定了后面所有代码的写法:print 只是把值显示出来,return 才把值交还给调用者。分不清这两件事,函数就没法组合。
时间轴
| 时间 | 内容 |
|---|---|
| 2:06:10 | 为什么需要函数:避免重复,封装复杂逻辑 |
| 2:07:40 | 定义函数的语法:def、名字、括号、冒号、缩进 |
| 2:09:11 | 调用函数;只定义不调用不会执行 |
| 2:10:29 | 命名规范:snake_case,动词开头 |
| 2:14:52 | 参数:让函数接受输入 |
| 2:17:34 | 位置参数与顺序;2:19:23 关键字参数 |
| 2:19:48 | 默认参数:无默认值的参数必须在前 |
| 2:23:01 | 局部变量与全局变量 |
| 2:29:07 | return:把结果交出去 |
| 2:33:17 | 用返回值组合表达式 |
| 2:35:57 | 返回多个值 |
| 2:38:25 | 外部工具:模块、包与 import 的开始 |
从「写一遍」到「定义一次」
如果只是打印一行,循环足够。但真实逻辑往往包含多个步骤:读取数据、计算、格式化、输出。每次都重写一遍既冗长,也容易改漏。
函数把这段逻辑包起来并命名,之后按名字调用:
def greet():
print("Hello")
greet()
# Hello
只定义不调用,什么也不会发生——定义只是把逻辑存进内存。必须显式调用 greet() 才执行。
语法拆解
def greet():
print("Hello")
def:定义函数的关键字;greet:函数名,遵循 snake_case,通常用动词开头;():参数列表,暂时为空;::块开始;- 缩进四空格:函数体。
命名建议:calculate_total、send_email,而不是 thing 或 my_func。函数名要让调用处读起来像一句话。
参数:让函数接受输入
上一节的 greet 是硬编码的,永远输出同一句话。参数让函数接受输入:
def greet(name):
print(f"Hello, {name}")
greet("Dave")
# Hello, Dave
greet("Alice")
# Hello, Alice
name 在函数内部就是一个变量,值由调用时传入。
位置参数
多个参数按顺序匹配:
def greet(first_name, last_name):
print(f"Hello, {first_name} {last_name}")
greet("Dave", "Ebbelaar")
# Hello, Dave Ebbelaar
少传或多传都会报错:
greet("Dave")
# TypeError: greet() missing 1 required positional argument: 'last_name'
顺序反了不会报错,但结果会错:
greet("Ebbelaar", "Dave")
# Hello, Ebbelaar Dave
关键字参数
在调用时写出参数名,顺序就不再重要:
greet(last_name="Ebbelaar", first_name="Dave")
# Hello, Dave Ebbelaar
作者的建议是:参数超过一个时,尽量显式写出名字。多敲几个字符,换来调用处一眼可读,也避免顺序错误。
默认参数
def greet(first_name="John", last_name="Doe"):
print(f"Hello, {first_name} {last_name}")
greet()
# Hello, John Doe
greet(first_name="Dave")
# Hello, Dave Doe
规则:有默认值的参数必须放在没有默认值的参数之后。否则 Python 无法判断省略的是哪一个:
def greet(first_name="John", last_name):
...
# SyntaxError: non-default argument follows default argument
多个参数的实际例子
def calculate_total(price, tax_rate, discount):
tax = price * tax_rate
final_price = price + tax - discount
print(f"Final price: {final_price:.2f}")
calculate_total(price=100, tax_rate=0.08, discount=10)
# Final price: 98.00
这里用关键字参数调用,顺序写错也不会算错;函数体内先算税、再得总价,步骤清晰。
局部变量与全局变量
Python 按「从上到下」执行,但函数内部有独立的作用域。
discount = 20 # 全局变量
def calculate_total(price):
discount = 10 # 局部变量,遮蔽同名的全局变量
return price - discount
print(calculate_total(100)) # 90
print(discount) # 20
要点:
- 函数内部赋值的变量是局部的,函数外访问不到;
- 同名时,函数内部优先使用局部变量;
- 函数外部的变量(全局)在函数内可以读取。
def show_discount():
print(discount)
discount = 20
show_discount()
# 20
反过来则不行:
def calculate_total(price):
local_discount = 10
return price - local_discount
print(local_discount)
# NameError: name 'local_discount' is not defined
作者的建议:不要把依赖藏在全局变量里。函数需要的输入,就写成参数。否则别人改动全局变量、或把函数挪到别的文件,函数就悄悄坏掉,而且报错位置离原因很远。
# 不推荐:依赖外部 discount
def calculate_total(price):
return price - discount
# 推荐:把依赖变成参数
def calculate_total(price, discount):
return price - discount
return:把结果交出去
这是本章最重要的一节。
def add_print(a, b):
print(a + b)
add_print(5, 10)
# 15
屏幕上出现了 15,但这个 15 已经消失了:没有任何变量持有它,无法再加 50,也无法存进文件。
return 则把值交给调用者:
def add(a, b):
return a + b
result = add(5, 10)
print(result) # 15
print(result + 50) # 65
区别可以概括为:
print | return | |
|---|---|---|
| 作用 | 显示给人看 | 把值交给程序 |
| 之后能否使用 | 不能 | 能,可存变量、参与运算 |
| 典型场景 | 调试、最终输出 | 绝大多数真实逻辑 |
一个常见的坑:函数里写了 print 却用变量接收,得到的是 None。
result = add_print(5, 10) # 屏幕打印 15
print(result) # None
用返回值组合
返回值可以参与表达式,这让函数像积木一样拼起来:
def double(n):
return n * 2
print(double(5) + double(3)) # 16
print(double(5) > 8) # True
返回多个值
用逗号分隔即可,调用方也按顺序接收:
def first_and_last(numbers):
first = numbers[0]
last = numbers[-1]
return first, last
f, l = first_and_last([1, 2, 3, 4, 5])
print(f) # 1
print(l) # 5
底层其实是返回了一个元组,所以也可以整体接收:
pair = first_and_last([1, 2, 3, 4, 5])
print(pair) # (1, 5)
print(pair[0]) # 1
从函数到模块
函数解决「同一文件内复用」,模块解决「跨文件复用」。这一节只是过渡,细节在下一章。
术语约定:
- 模块(module):一个
.py文件; - 包(package):包含多个模块的文件夹;
- 函数:模块里的具体工具;
- 类:构造对象的蓝图(第五章之后讲)。
可以这样记:模块是工具箱,包是放多个工具箱的车库,函数是箱里的锤子,类是造工具的图纸。
import math
print(math.sqrt(16)) # 4.0
整份模块导入,用 模块名.函数名 调用。也可以只取需要的部分:
from math import sqrt, pi
print(sqrt(16)) # 4.0
print(pi) # 3.141592653589793
区别在于命名空间:from ... import ... 之后,名字直接可用,但也更容易与自己的变量重名。
本章结论
def 名字(参数):定义函数;只定义不调用不会执行。- 函数名用 snake_case,动词开头,调用处要读得通。
- 参数让函数动态化;多参数时优先用关键字参数。
- 有默认值的参数必须放在最后。
- 函数内部赋值的变量是局部的;不要让函数依赖全局变量,把依赖写成参数。
return把值交给调用者,print只是显示;绝大多数真实逻辑要用return。- 函数可以返回多个值,本质是返回元组。
- 模块是
.py文件,包是模块的文件夹;import让代码跨文件复用。
理解检验
1. 为什么只写 def greet(): ... 而不调用,运行脚本什么也不会发生?
参考答案:def 语句的作用是把函数体编译成一个函数对象并绑定到名字上,它不会执行函数体。函数体只在显式调用 greet() 时运行。所以脚本从上到下执行完定义后正常结束,没有任何输出。
2. 下面两种写法的函数,result = add(5, 10) 之后 result 分别是什么?为什么?
def add_print(a, b):
print(a + b)
def add(a, b):
return a + b
参考答案:add_print 没有 return,Python 默认返回 None,所以 result 是 None,屏幕上只会出现 15。add 用 return 把 15 交给调用者,result 就是 15,可以继续参与运算。print 与 return 的区别在于「显示」和「交出」。
3. 把 def greet(first_name="John", last_name): 这样定义会报什么错?规则是什么?
参考答案:报 SyntaxError: non-default argument follows default argument。Python 要求有默认值的参数放在所有无默认值参数之后。原因是调用时省略参数只能从右往左匹配,如果默认参数在前、必填参数在后,解释器无法判断某个位置省略的是哪一个。正确写法是 def greet(first_name, last_name="Doe")。
4. 下面代码输出什么?如果想让函数使用外部的 discount,应该怎么改?
discount = 20
def calculate_total(price):
discount = 10
return price - discount
print(calculate_total(100))
print(discount)
参考答案:输出 90 和 20。函数内的 discount = 10 创建的是局部变量,遮蔽了同名的全局变量;函数返回 100 - 10 = 90,而函数外的全局 discount 没有被修改,仍是 20。若希望函数使用外部的值,正确做法不是依赖全局变量,而是把它变成参数:def calculate_total(price, discount): return price - discount,调用时传入 discount=20。
5. 为什么说「函数返回多个值」本质上还是返回一个值?
参考答案:return first, last 会先把两个值组成一个元组 (first, last) 再返回,所以函数真正的返回值是一个元组。调用处的 f, l = ... 只是在把这个元组解包到两个名字上;写成 pair = first_and_last(...),pair 就是那个二元组。
与前后章的关系
函数把前四章的所有零件组装成了可复用的单元,也让代码第一次具备「模块化」的形状。下一章从「同一文件内复用」跨到「跨文件复用」:模块与包的导入方式、pip 安装第三方库、requirements.txt 锁定依赖,以及用 requests 调用第一个 API。之后所有章节都以函数为单位组织代码。
What this chapter covers
This is chapter 4 of the long-form video, covering 2:06:10–2:49:11. The previous three chapters gathered the parts; this one assembles them — naming a piece of logic, packaging it, calling it repeatedly. It covers defining and calling functions, parameters (positional, keyword, defaults), local versus global variables, return values, and finally modules and import, the real entrance to Python's ecosystem.
One distinction drives the whole chapter and all later code: print only displays a value; return hands it back to the caller. Without that distinction, functions cannot be composed.
Timeline
| Time | Topic |
|---|---|
| 2:06:10 | Why functions: avoid repetition, package complex logic |
| 2:07:40 | Syntax: def, name, parentheses, colon, indentation |
| 2:09:11 | Calling; defining alone does nothing |
| 2:10:29 | Naming: snake_case, verb first |
| 2:14:52 | Parameters: giving functions input |
| 2:17:34 | Positional parameters and order; 2:19:23 keyword arguments |
| 2:19:48 | Defaults: parameters without defaults must come first |
| 2:23:01 | Local versus global variables |
| 2:29:07 | return: handing the result back |
| 2:33:17 | Composing expressions with return values |
| 2:35:57 | Returning several values |
| 2:38:25 | External tools: modules, packages, the start of import |
From writing once to defining once
A loop handles a single repeated line. Real logic has several steps: read data, compute, format, output. Rewriting it each time is verbose and easy to break.
A function names that logic and lets you call it by name:
def greet():
print("Hello")
greet()
# Hello
Defining without calling does nothing — the definition only stores the logic in memory. greet() executes it.
Anatomy
def greet():
print("Hello")
def: the keyword that defines a function;greet: the name, snake_case, usually a verb;(): the parameter list, empty for now;:: starts the block;- four spaces: the body.
Prefer names such as calculate_total or send_email over thing or my_func: the call site should read like a sentence.
Parameters: giving functions input
The greet above is hard-coded and always prints the same thing. Parameters make it dynamic:
def greet(name):
print(f"Hello, {name}")
greet("Dave")
# Hello, Dave
greet("Alice")
# Hello, Alice
Inside the function, name is just a variable whose value comes from the call.
Positional parameters
Several parameters match by position:
def greet(first_name, last_name):
print(f"Hello, {first_name} {last_name}")
greet("Dave", "Ebbelaar")
# Hello, Dave Ebbelaar
Too few or too many arguments raise an error:
greet("Dave")
# TypeError: greet() missing 1 required positional argument: 'last_name'
Reversed order does not raise — it silently produces the wrong result:
greet("Ebbelaar", "Dave")
# Hello, Ebbelaar Dave
Keyword arguments
Naming the parameters at the call site removes the ordering problem:
greet(last_name="Ebbelaar", first_name="Dave")
# Hello, Dave Ebbelaar
The instructor's recommendation: with more than one parameter, name them. A few extra characters buy a readable call and prevent ordering bugs.
Default parameters
def greet(first_name="John", last_name="Doe"):
print(f"Hello, {first_name} {last_name}")
greet()
# Hello, John Doe
greet(first_name="Dave")
# Hello, Dave Doe
Rule: parameters with defaults must follow those without. Otherwise Python cannot tell which argument was omitted:
def greet(first_name="John", last_name):
...
# SyntaxError: non-default argument follows default argument
A realistic example
def calculate_total(price, tax_rate, discount):
tax = price * tax_rate
final_price = price + tax - discount
print(f"Final price: {final_price:.2f}")
calculate_total(price=100, tax_rate=0.08, discount=10)
# Final price: 98.00
Called with keyword arguments, a wrong order still computes the right thing; the body computes tax first, then the total, keeping the steps visible.
Local and global variables
Python executes top to bottom, but a function body has its own scope.
discount = 20 # global
def calculate_total(price):
discount = 10 # local, shadows the global
return price - discount
print(calculate_total(100)) # 90
print(discount) # 20
Key points:
- a variable assigned inside a function is local and invisible outside;
- when names collide, the local one wins inside the function;
- globals are readable from inside functions.
def show_discount():
print(discount)
discount = 20
show_discount()
# 20
The reverse fails:
def calculate_total(price):
local_discount = 10
return price - local_discount
print(local_discount)
# NameError: name 'local_discount' is not defined
The advice: do not hide dependencies in globals. If a function needs a value, make it a parameter. Otherwise changing the global elsewhere, or moving the function to another file, silently breaks it — and the error appears far from the cause.
# avoid: hidden dependency
def calculate_total(price):
return price - discount
# prefer: explicit input
def calculate_total(price, discount):
return price - discount
return: handing the result back
This is the most important section of the chapter.
def add_print(a, b):
print(a + b)
add_print(5, 10)
# 15
The 15 appears on screen and is then gone: no variable holds it, so it cannot be added to 50 or written to a file.
return hands the value to the caller:
def add(a, b):
return a + b
result = add(5, 10)
print(result) # 15
print(result + 50) # 65
The difference:
print | return | |
|---|---|---|
| Purpose | show a value to a human | hand a value to the program |
| Usable afterwards | no | yes — store it, compute with it |
| Typical use | debugging, final output | almost all real logic |
A common trap: a function that prints, captured into a variable, yields None.
result = add_print(5, 10) # prints 15
print(result) # None
Composing with return values
Returned values participate in expressions, so functions behave like building blocks:
def double(n):
return n * 2
print(double(5) + double(3)) # 16
print(double(5) > 8) # True
Returning several values
Separate with commas; the caller unpacks in order:
def first_and_last(numbers):
first = numbers[0]
last = numbers[-1]
return first, last
f, l = first_and_last([1, 2, 3, 4, 5])
print(f) # 1
print(l) # 5
Under the hood it returns one tuple, so it can also be captured whole:
pair = first_and_last([1, 2, 3, 4, 5])
print(pair) # (1, 5)
print(pair[0]) # 1
From functions to modules
Functions reuse logic within one file; modules reuse it across files. This is only a transition; the next chapter goes deeper.
Terminology:
- module: a
.pyfile; - package: a folder containing modules;
- function: a specific tool inside a module;
- class: a blueprint for building objects (covered later).
A useful image: a module is a toolbox, a package is a garage of toolboxes, a function is a hammer in a box, and a class is a blueprint for making tools.
import math
print(math.sqrt(16)) # 4.0
Importing the module and calling module.function. Or take only what is needed:
from math import sqrt, pi
print(sqrt(16)) # 4.0
print(pi) # 3.141592653589793
The difference is the namespace: after from ... import ... the names are directly available, and also easier to collide with your own variables.
Takeaways
def name(params):defines a function; defining alone does nothing.- Use snake_case names that start with a verb and read well at the call site.
- Parameters make functions dynamic; prefer keyword arguments once there is more than one.
- Parameters with defaults must come last.
- Variables assigned inside a function are local; do not rely on globals — pass dependencies in.
returnhands the value back,printonly displays it.- Returning several values really returns a tuple.
- A module is a
.pyfile, a package is a folder of modules;importenables reuse across files.
Check your understanding
1. Why does running a script that only defines def greet(): ... produce no output?
Reference answer: A def statement compiles the body into a function object and binds it to a name; it does not execute the body. The body runs only when greet() is called. So the script finishes after the definition with nothing printed.
2. After result = add(5, 10), what is result for each version, and why?
def add_print(a, b):
print(a + b)
def add(a, b):
return a + b
Reference answer: add_print has no return, so Python returns None by default and result is None; only 15 appears on screen. add returns 15, so result is 15 and can be used in further computation. The difference is display versus hand-back.
3. What error does def greet(first_name="John", last_name): raise, and what is the rule?
Reference answer: SyntaxError: non-default argument follows default argument. Parameters with defaults must come after all parameters without them, because omitted arguments are matched from the right; if a defaulted parameter comes first and a required one follows, the interpreter cannot tell which position was omitted. The correct form is def greet(first_name, last_name="Doe").
4. What does the code print, and how should the function obtain discount from outside?
discount = 20
def calculate_total(price):
discount = 10
return price - discount
print(calculate_total(100))
print(discount)
Reference answer: 90 and 20. The assignment inside the function creates a local variable that shadows the global; the function returns 100 - 10 = 90, while the global discount stays 20. The right fix is not to depend on the global but to take it as a parameter: def calculate_total(price, discount): return price - discount, called with discount=20.
5. Why is "returning several values" really returning one value?
Reference answer: return first, last first builds the tuple (first, last) and returns that tuple, so the function's real return value is a single tuple. At the call site, f, l = ... unpacks the tuple into two names; writing pair = first_and_last(...) shows the tuple itself.
How it connects
Functions turn the parts from the first four chapters into reusable units and give code a modular shape. The next chapter crosses from reuse within a file to reuse across files: module and package imports, installing third-party libraries with pip, pinning dependencies in requirements.txt, and calling a first API with requests. Every later chapter organises code in functions.