
编程语言中的变量与函数:用示例理解基本用法
变量和函数是编程中最基础、也最常用的两个概念。变量用来给数据起名字,函数用来把一段操作组织起来并在需要时调用。无论是处理用户输入、计算结果,还是读取文件和调用接口,程序通常都在不断地保存数据、处理数据,再把处理步骤组合起来。
不同编程语言对变量和函数有不同规定:有的要求声明变量类型,有的允许在运行时改变类型;有的函数必须写明返回类型,有的则不需要。本文选用 Python 3 作为示例语言,重点讲解通用思路,并说明哪些细节属于 Python 的语言规则。文中的代码可以复制到 Python 3 环境中运行。
一、变量:给数据一个可读的名字
变量可以理解为程序中用于访问数据的名称。比如:
name = "Lin"
age = 20
print(name)
print(age)
在 Python 中,赋值符号 = 表示把右侧的值绑定到左侧的名称。它不是数学等式:执行 age = 20 后,程序可以通过 age 这个名字取得整数 20。后续重新赋值会让这个名字关联到新的值:
score = 80
score = score + 5
print(score) # 85
第二行先读取当前的 score,加上 5,再把结果重新赋给 score。这也是“更新变量”的常见写法。对支持相应运算的类型,Python 还提供了简写形式:
score = 80
score += 5
print(score) # 85
变量名怎么起
清楚的变量名能帮助读者理解代码,不必频繁追查数据含义。Python 变量名可以使用字母、数字和下划线,但不能以数字开头,也不能使用语言关键字作为名称。大小写有区别,例如 user_name 和 userName 是两个不同的名字。
user_name = "Mina"
item_count = 3
_has_access = True
# 以下是无效或不推荐的写法:
# 2nd_item = "book" # 不能以数字开头
# class = "A" # class 是 Python 关键字
一般可用有意义的名词描述数据,例如 price、students、is_active。不宜把 x、data1 用作长期存在且含义不明的变量名;但在短小的数学公式或循环中,i、x 这类简短名称有时是合理的。命名不是越长越好,关键是让含义清楚。
二、值的类型与变量的类型
程序中的数据有不同类型。常见类型包括整数 int、小数 float、字符串 str、布尔值 bool,以及列表 list、字典 dict 等容器。
count = 12
temperature = 21.5
message = "任务完成"
is_ready = True
tags = ["python", "basic"]
profile = {"name": "Mina", "level": 2}
print(type(count)) # <class 'int'>
print(type(message)) # <class 'str'>
print(type(profile)) # <class 'dict'>
Python 是动态类型语言:名称不需要预先声明类型,同一个名称在不同时间可以关联不同类型的值。
value = 10
value = "ten"
print(value) # ten
这并不表示所有类型都能随意混用。例如,字符串和整数不能直接相加:
# 运行会产生 TypeError
# total = "数量:" + 3
如果确实需要把整数显示在字符串中,可以显式转换,或使用格式化字符串:
count = 3
print("数量:" + str(count))
print(f"数量:{count}")
动态类型让代码写起来灵活,但也意味着一些问题可能到运行时才暴露。明确数据的预期类型、在输入边界进行校验、为关键函数写测试,都能减少这类错误。需要更严格约束时,可以使用类型注解;但要记住,Python 的类型注解默认主要用于编辑器、静态检查工具和阅读理解,解释器通常不会仅凭注解自动拒绝不符合类型的值。
def double(value: int) -> int:
return value * 2
print(double(4)) # 8
print(double("ha")) # hahaha,普通运行时不会自动按注解报错
如果项目需要在开发阶段检查类型,可使用支持 Python 的静态类型检查工具;运行时的外部输入仍应通过实际校验逻辑处理。
三、赋值、对象与可变数据
理解变量时,一个容易忽略的边界是:变量名和数据对象不是一回事。赋值通常是让名称指向某个对象,并不总是复制对象。对于不可变对象(如整数、字符串、元组),看起来像“修改”的操作通常会产生新值;列表和字典等可变对象则可以在原对象上发生变化。
numbers = [1, 2]
other = numbers
other.append(3)
print(numbers) # [1, 2, 3]
print(other) # [1, 2, 3]
这里 numbers 和 other 指向同一个列表,所以通过其中一个名称修改列表,另一个名称看到的内容也会变化。如果希望得到一份浅拷贝,可以使用列表的 copy() 方法:
numbers = [1, 2]
other = numbers.copy()
other.append(3)
print(numbers) # [1, 2]
print(other) # [1, 2, 3]
浅拷贝只复制最外层容器。若列表内部还有列表等可变对象,内部对象仍可能共享;需要独立复制嵌套结构时,应根据数据结构选择合适的复制方法,而不是假设 copy() 会递归复制所有内容。
四、作用域:名称在哪里有效
作用域决定了名称可以在哪里被访问。函数内部创建的局部变量通常只在函数执行期间可用;在函数外定义的全局变量可以在模块范围内访问。局部变量与外部同名时,会遮蔽外部名称,容易让代码难以理解。
message = "模块级"
def show_message():
message = "函数内"
print(message)
show_message() # 函数内
print(message) # 模块级
上例中,函数里的 message 是一个新的局部名称,并没有修改外面的变量。通常优先通过参数把数据传入函数、通过返回值把结果传出,而不是依赖可随处修改的全局变量。
def make_greeting(name):
return f"你好,{name}"
greeting = make_greeting("Mina")
print(greeting)
Python 允许使用 global 声明要在函数内重新绑定模块级名称,也允许使用 nonlocal 影响外层函数的局部名称。不过这类写法会增加数据流的隐蔽性,除非确实需要,否则通常不如显式参数和返回值清晰。注意,对列表这类可变对象调用 append() 属于修改对象,并不等同于在函数内重新给全局名称赋值。
五、函数:把一项操作组织起来
函数是一段有名称、可重复调用的代码。它可以接收输入(参数),执行操作,并选择返回结果。定义函数使用 def,函数体需要缩进:
def add(a, b):
result = a + b
return result
answer = add(3, 5)
print(answer) # 8
函数定义本身通常不会执行函数体;调用 add(3, 5) 时才会把实际值传给参数 a 和 b,然后执行函数体。return 用来结束函数并返回结果。返回值可以赋给变量、传给另一个函数,或直接用于表达式。
def square(number):
return number * number
print(square(4)) # 16
print(square(3) + 1) # 10
函数没有显式执行 return 时,Python 会返回 None。因此,打印结果和返回结果不是一回事:
def print_total(a, b):
print(a + b)
result = print_total(2, 3) # 屏幕输出 5
print(result) # None
print() 是向输出位置显示内容;return 是把值交还给调用者。需要让后续代码继续计算时,通常应该返回值,而不只是打印值。
六、参数:函数如何接收输入
参数是函数定义中的输入名称,调用时提供的具体数据通常称为实参。Python 支持位置参数和关键字参数。位置参数按顺序对应;关键字参数通过参数名明确对应关系。
def describe_item(name, price):
return f"{name}:{price} 元"
print(describe_item("笔记本", 12))
print(describe_item(price=12, name="笔记本"))
关键字调用在参数较多时更易读,也减少因顺序错误传入数据的风险。必需参数没有提供时会产生 TypeError;不应把“参数数量不限”当作 Python 函数的默认行为。
参数可以设置默认值。默认值适合表达一个稳定、简单的默认选项:
def greet(name, greeting="你好"):
return f"{greeting},{name}"
print(greet("Lin")) # 你好,Lin
print(greet("Lin", "早上好")) # 早上好,Lin
一个重要边界是:默认值表达式在函数定义时计算一次,而不是每次调用时重新创建。将可变对象(如列表、字典)直接作为默认值,可能导致不同调用共享同一对象。
# 不推荐:所有未传入 items 的调用共享同一个列表
def add_item(item, items=[]):
items.append(item)
return items
# 推荐:用 None 表示未提供,并在调用时新建列表
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
print(add_item("a")) # ['a']
print(add_item("b")) # ['b']
Python 还支持可变数量参数。*args 收集额外的位置参数为元组,**kwargs 收集额外的关键字参数为字典:
def summarize(title, *values, unit="项", **options):
total = sum(values)
return {
"title": title,
"total": total,
"unit": unit,
"options": options,
}
result = summarize("本周", 2, 3, 5, unit="件", color="blue")
print(result)
这里 unit 因为位于 *values 之后,调用时需要作为关键字参数提供。*args 和 **kwargs 使函数接口更灵活,但也可能让接口接受过多意料之外的输入。对需要稳定行为的业务函数,应优先显式列出参数,并检查允许的选项,而不是盲目接收和转发所有参数。
参数数量和形式最终受函数签名约束。某些参数可设为必需,某些可设默认值;调用时也可能因为漏传、重复赋值、位置参数顺序不符等原因报错。Python 不会自动为任意类型、任意内容的参数提供业务层面的有效性保证。例如,函数可以收到负数,但“数量必须大于零”通常要由函数明确检查。
def reserve_seats(count):
if not isinstance(count, int) or isinstance(count, bool):
raise TypeError("座位数必须是整数")
if count <= 0:
raise ValueError("座位数必须大于 0")
return f"已预留 {count} 个座位"
print(reserve_seats(2))
这里显式排除了布尔值,是因为在 Python 中 bool 是 int 的子类,isinstance(True, int) 的结果为真。是否需要如此严格,取决于函数的实际业务规则。
七、边界与常见错误
1. 把赋值和比较混淆
= 用于赋值;== 用于比较是否相等。把两者混淆会导致语法错误或判断逻辑错误。
number = 5
if number == 5:
print("number 是 5")
2. 以为字符串和数字可以直接拼接
字符串与整数不是同一种类型,直接用 + 拼接通常会报错。需要先转换,或使用格式化字符串。
age = 20
print(f"年龄:{age}")
3. 函数只打印,没有返回
如果调用者要继续使用计算结果,应使用 return。否则函数默认返回 None,后续运算可能失败。
def get_double(value):
return value * 2
result = get_double(6)
print(result + 1) # 13
4. 使用尚未赋值的名称
读取一个当前作用域中尚未绑定的局部变量,会产生 UnboundLocalError;使用未定义名称则会产生 NameError。应检查变量是否在所有执行路径上都先得到赋值。
def choose_label(is_ready):
if is_ready:
label = "就绪"
else:
label = "等待"
return label
print(choose_label(False))
5. 默认参数中放可变对象
如前文所示,列表或字典作为默认参数时可能在多次调用间保留内容。通常以 None 作为默认值,并在函数体中创建新对象。
6. 把类型注解当作运行时校验
注解有助于说明接口、获得编辑器提示,并供静态检查工具分析,但普通 Python 运行时通常不会据此自动验证参数。对于文件内容、网络请求、表单字段等外部数据,仍需显式检查类型、范围和格式。
八、变量与函数如何配合:一个可运行示例
下面用一个简单的订单小计函数说明变量、参数、返回值和校验如何组合。该示例不涉及实际支付或税费规则,只演示基本计算。
def calculate_subtotal(prices, quantity=1):
"""计算一组商品价格乘以购买数量后的金额。"""
if not isinstance(prices, list):
raise TypeError("prices 必须是列表")
if not prices:
raise ValueError("prices 不能为空")
if not isinstance(quantity, int) or isinstance(quantity, bool):
raise TypeError("quantity 必须是整数")
if quantity <= 0:
raise ValueError("quantity 必须大于 0")
total = 0
for price in prices:
if not isinstance(price, (int, float)) or isinstance(price, bool):
raise TypeError("每个价格都必须是数字")
if price < 0:
raise ValueError("价格不能为负数")
total += price
return total * quantity
prices = [12.5, 8, 4.5]
quantity = 2
subtotal = calculate_subtotal(prices, quantity)
print(f"小计:{subtotal}")
在这段代码中,prices 和 quantity 是函数的输入参数,也是在函数调用处保存数据的变量;total 是函数内部的局部变量;subtotal 接收函数返回结果。函数先检查输入是否符合此示例设定的规则,再进行计算。
真实业务还要考虑货币精度、折扣、税费、币种和舍入规则。用二进制浮点类型 float 处理金额时,某些十进制小数不能被精确表示,因此涉及结算的程序通常应根据业务需求使用整数最小货币单位,或使用 Python 的 decimal.Decimal 并制定明确的精度与舍入策略。不能把示例中的简单求和直接当成完整的支付实现。
九、函数应该做多大
函数适合封装一个有清晰目的的操作。一个函数太长、同时读取文件、修改多个全局变量、做网络请求又计算结果,往往难以测试和复用;但把每一行都拆成单独函数,也会让阅读路径变得琐碎。可以从以下问题判断是否适合抽取函数:
- 这段操作是否会重复出现?重复的校验、格式化或计算通常值得复用。
- 这段逻辑能否用一个明确的动词或短语描述?例如“计算折扣后价格”或“解析日期”。
- 输入和输出是否清楚?参数与返回值明确,通常比依赖隐藏状态的代码更容易理解。
- 函数是否同时承担多个职责?如果修改其中一部分会牵连许多无关逻辑,可以考虑拆分。
函数不一定必须返回值。写入日志、更新界面或修改某个对象状态的函数可能主要产生副作用。不过,副作用越多,调用者越需要了解其行为;清晰命名、明确文档和针对性的测试就越重要。
十、变量和函数与安全:能做什么,不能做什么
变量和函数本身并不能自动保证安全。安全性来自对数据来源、权限、校验方式和执行行为的正确处理。用户输入、环境变量、配置文件内容和网络数据都应视作可能不符合预期,不能仅因为它们被存进变量或传入函数,就认为它们可信。
尤其不要把不可信字符串直接交给 eval() 或 exec() 执行。这类函数会把字符串当作 Python 表达式或语句运行,可能导致任意代码被执行。它们不适合作为解析普通数字、配置或用户输入的捷径。若输入预期是整数,应使用明确的转换并处理异常:
raw_value = "42"
try:
value = int(raw_value)
except ValueError:
print("输入不是有效整数")
else:
print(value)
如果需要从 JSON 文本读取结构化数据,应使用专门的 JSON 解析器,并在解析后继续校验字段、类型和值的范围。解析成功只说明文本符合 JSON 语法,不代表其中的数据满足业务规则。调用数据库、运行系统命令、访问文件时也应使用相应的安全接口和权限控制;变量命名或函数抽取并不会代替这些防护。
结语
变量让程序能够以名称保存和访问数据,函数让程序能够把操作组织成可复用的单元。掌握赋值与类型、对象共享与作用域、参数与默认值、返回值与副作用,能解决大量日常编程问题。写代码时,优先选择含义明确的变量名和函数名,让输入、输出与限制清楚可见;对外部数据进行实际校验;避免依赖隐式共享状态,也不要把类型注解误当作运行时保障。
这些基础规则适用于许多语言,但具体语法和边界会不同。学习其他语言时,可以继续追问几个问题:变量是否需要声明类型?赋值会复制数据还是共享对象?函数参数如何传递?缺少返回值时会怎样?默认参数何时计算?弄清这些差异,才能把通用概念转化成特定语言中可靠、可维护的代码。









