Python 基础能力:函数、模块与常用数据结构精华(个人学习笔记)

当我不满足于只写几行小脚本,希望用 Python 写点“像样的代码”时,需要的就是这里这几块——函数、模块、字符串和那几种最常用的数据结构


一、用函数消灭重复代码(这点和 Java 完全共通)

在 Java 里,我们早就习惯了“别写重复代码”。在 Python 里也是一样,只不过写法更轻量一点。

1.1 定义与调用

def fac(num):
    """计算 num 的阶乘"""
    result = 1
    for n in range(1, num + 1):
        result *= n
    return result

m = int(input("m = "))
n = int(input("n = "))
print(fac(m) // (fac(n) * fac(m - n)))

我给自己记了三个关键点:

  • def 函数名(参数列表): 开头,下面缩进就是函数体;
  • return 返回结果,不写 return 默认返回 None
  • 函数第一行的三引号字符串是文档,可以被 help() 直接看到。

1.2 默认参数 & 可变参数:不用靠重载也能很灵活

from random import randint  # 导入模块里的函数random 是 Python 自带的随机数模块

def roll_dice(n=2):     # n=2表示调用该函数没有传值,默认传2
    """摇 n 颗骰子"""      # 三引号 """ ... """ 是函数文档字符串(docstring)也能被 help(roll_dice) / IDE 提示看到
    total = 0
    for _ in range(n):       # _ 默认写法 在 Python 里常被当作“我不使用这个循环变量”的标记   
                                          # range(n) 生成一个从 0 到 n-1 的序列
        total += randint(1, 6)
    return total

def add(*args):            # *args 表示“可变参数”(variable-length arguments),它会把传进来的多参数打包成一个元组(tuple)
    """任意个参数求和"""
    total = 0
    for v in args:
        total += v
    return total

print(roll_dice())       # 使用默认值 n=2
print(roll_dice(3))      # 显式传入 3
print(add(1, 2, 3, 4))   # 可变参数

对比 Java,我觉得最爽的一点是:Python 根本不需要写一堆重载,直接靠:

  • 默认参数,把 80% 的场景兜住;
  • *args / **kwargs,搞定“参数个数不定”的情况。

再加上 关键字参数 之后,可读性也很高:

def add3(a=0, b=0, c=0):
    return a + b + c

print(add3(c=10, a=1))   # 通过名字传参,顺序可变

二、模块化与入口:写脚本也要有“项目感”

大白话版理解:

  • 一个 .py 文件就是一个模块;
  • 想用谁,就 import 谁。

常见的导入方式:

import math
from math import sqrt
import module1 as m1

我现在基本强迫自己所有脚本都用这种入口写法,避免“导入就乱跑”的尴尬:

def main():
    # TODO: 你的主流程
    pass

if __name__ == "__main__":
    main()

记忆点:“谁当入口跑,谁的 __name__ 就等于 "__main__"。被别的脚本 import 时,就安静地只暴露函数 / 类,不去乱执行。


三、字符串:我用得最频繁的内建类型

3.1 定义与转义

s1 = 'hello, world'
s2 = "hello, world"
s3 = """
多行
字符串
"""

s4 = 'I\'m OK'
s5 = r"C:\Users\name\Desktop"   # 原始字符串,不处理转义

多行字符串 + 原始字符串这两个特性,对写配置、写正则等场景非常友好,比 Java 里各种转义舒服太多。

3.2 常用操作

s = "hello, world!"

print(len(s))              # 长度
print(s.upper())           # 全大写
print(s.title())           # 每个单词首字母大写
print(s.startswith("hel"))
print(s.endswith("!"))

print("world" in s)        # 子串判断

print(s[0:5])              # 切片: hello
print(s[7:])               # world!
print(s[::-1])             # 反转

切片语法一开始有点不习惯,但习惯之后非常顺手,尤其是那些“一行搞定”的小操作。

3.3 格式化(再次强调 f-string)

name = "张三"
age = 18
print(f"{name} 的年龄是 {age}")

对比 Java 里 String.format 或拼接字符串,f-string 基本可以盖棺定论:能用就用它


四、列表 / 元组 / 集合 / 字典:Python 的“容器四件套”

我脑子里现在已经给这四个东西固定了一个“印象位”:

  • 列表 list:可变、有序,类比 Java 的 ArrayList
  • 元组 tuple:不可变、有序,适合“一组固定结构的数据”-我自己认为有点类似枚举
  • 集合 set:无序、不重复,做去重和集合运算超顺手;
  • 字典 dict:键值对,类比 Java 的 HashMap<K, V>

4.1 列表 list(可变、有序)

nums = [1, 3, 5, 7, 100]

nums.append(200)
nums.insert(1, 400)
nums += [1000, 2000]

print(nums)
print(len(nums))        # 长度
print(nums[0], nums[-1])

# 遍历
for x in nums:
    print(x)

# 切片
print(nums[1:4])
print(nums[::-1])       # 反转

# 删除
nums.remove(3)          # 按值删
nums.pop(0)             # 按下标删
nums.clear()            # 清空

列表推导式是 Python 的“味道”之一:

squares = [x * x for x in range(1, 11)]。 # (左闭右开)
evens = [x for x in range(1, 101) if x % 2 == 0]

写多了之后,会自然从“for 循环造列表”转成这种更简洁的写法。

4.2 元组 tuple(不可变、有序)

适合作为“只读数据结构”或“多值返回”。对我来说,有两个典型用法:

  • 函数一次性返回多值:return x, y
  • 表示一个不会改的固定结构,比如坐标、配置项。
person = ("张三", 18, "北京")

name, age, city = person    # 拆包

# person[0] = "李四"  # 会报错,因为元组不可修改

4.3 集合 set(无序、不重复)

set1 = {1, 2, 3, 3, 2}
set2 = set(range(1, 5))

set1.add(4)
set1.discard(2)

print(set1 & set2)    # 交集
print(set1 | set2)    # 并集
print(set1 - set2)    # 差集
print(set1 ^ set2)    # 对称差

非常适合:

  • 去重;
  • 快速判断成员是否存在(期望 O(1))。

4.4 字典 dict(键值对)

类似 Java 的 HashMap<K, V>,但语法轻量很多,也没有泛型那套负担。

scores = {"Alice": 95, "Bob": 80}

scores["Bob"] = 88
scores["Tom"] = 70

print(scores["Alice"])
print(scores.get("Jack", 60))   # 不存在给默认值

for name, score in scores.items():
    print(name, score)

scores.pop("Alice", None)       # 删除键
scores.clear()                  # 清空

字典推导式也很常用:

squares = {x: x * x for x in range(1, 6)}

五、生成器与列表生成式:写出高效又优雅的代码

5.1 列表生成式 vs 生成器表达式

# 列表生成式:一次性生成全部元素,占用内存
f = [x * x for x in range(1, 1000)]

# 生成器表达式:按需生成,节省内存
g = (x * x for x in range(1, 1000))

for val in g:
    print(val)

简单理解:列表生成式换成圆括号,就变成生成器表达式
如果数据很多,但我只是顺序消费一遍,那用生成器更省内存。

5.2 用 yield 定义生成器:斐波那契数列

def fib(n):
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b
        yield a

for v in fib(10):
    print(v)

yield 的直观感觉是:函数不会一次性把结果全算完,而是每次“吐”一个值出来
对流式处理、懒加载这种场景非常自然。


六、作用域与全局变量:尽量少用“全局大杀器”

6.1 作用域查找顺序(LEGB)

Python 的变量查找顺序可以用一个缩写记:LEGB

  • L:局部作用域(Local)
  • E:嵌套作用域(Enclosing)
  • G:全局作用域(Global)
  • B:内置作用域(Built-in)

配合一个小例子记一下就够用了:

def outer():
    b = "outer"

    def inner():
        c = "inner"
        print(a)  # 全局
        print(b)  # 嵌套
        print(c)  # 局部

    inner()

a = "global"
outer()

6.2 修改全局变量:global(一般不推荐多用)

a = 100

def foo():
    global a
    a = 200

foo()
print(a)   # 200

我的给自己定的“底线”是:

  • 尽量少用全局变量,多用参数传递和返回值;
  • 如果真的要改全局,就显式写 global,提醒一下自己;
  • 这点和我在 Java 里控制静态全局状态的思路是完全一致的:越少越安全

七、这一篇我给自己的小结

这一篇主要是把“写脚本”升级为“写可维护代码”需要的那几块打包了一下:

  • 函数 + 模块:代码有名字、有边界、有入口;
  • 字符串 + 四大容器:绝大多数业务数据都能直接用它们搞定;
  • 推导式 + 生成器:在可读性的前提下,让代码简洁一点;
  • 作用域意识:少用全局状态,别给未来的自己挖坑。

对我来说,一个很实用的练习方式是:
挑几个平时用 Java + Shell 写的小工具(比如解析日志、整理配置、抓个简单页面),强行用 Python 重写一遍
并刻意要求自己做到:

  • 所有逻辑都收敛在函数里;
  • 用 list []/ dict {}/ set {} /元组 ()把数据结构表达清楚;
  • 入口统一放在 if __name__ == "__main__": 下面。

等这些习惯自然成型之后,我再回头看这篇笔记,基本就是一个“快速刷新记忆”的 cheatsheet 了。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容