编程语言中的变量与函数:用示例理解基本用法

科技资讯4小时前发布 jdkjadmin
0 00
编程语言中的变量与函数:用示例理解基本用法

编程语言中的变量与函数:用示例理解基本用法

变量和函数是编程中最基础、也最常用的两个概念。变量用来给数据起名字,函数用来把一段操作组织起来并在需要时调用。无论是处理用户输入、计算结果,还是读取文件和调用接口,程序通常都在不断地保存数据、处理数据,再把处理步骤组合起来。

不同编程语言对变量和函数有不同规定:有的要求声明变量类型,有的允许在运行时改变类型;有的函数必须写明返回类型,有的则不需要。本文选用 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 语法,不代表其中的数据满足业务规则。调用数据库、运行系统命令、访问文件时也应使用相应的安全接口和权限控制;变量命名或函数抽取并不会代替这些防护。

结语

变量让程序能够以名称保存和访问数据,函数让程序能够把操作组织成可复用的单元。掌握赋值与类型、对象共享与作用域、参数与默认值、返回值与副作用,能解决大量日常编程问题。写代码时,优先选择含义明确的变量名和函数名,让输入、输出与限制清楚可见;对外部数据进行实际校验;避免依赖隐式共享状态,也不要把类型注解误当作运行时保障。

这些基础规则适用于许多语言,但具体语法和边界会不同。学习其他语言时,可以继续追问几个问题:变量是否需要声明类型?赋值会复制数据还是共享对象?函数参数如何传递?缺少返回值时会怎样?默认参数何时计算?弄清这些差异,才能把通用概念转化成特定语言中可靠、可维护的代码。

© 版权声明

相关文章