3. Python 基础回顾(上):环境与核心语法

各位同学,欢迎来到第三章。说实话,我见过太多人在环境配置上栽跟头,一搞就是半天。咱们做无人机航线规划,Python 环境必须干净、可控。今天我就带你从零搭一套靠谱的开发环境,再把 Python 的核心语法过一遍。

本章核心目标:搭建隔离的 Python 虚拟环境,掌握数据类型、控制流、函数和面向对象基础。这些是后续写航线算法、解析协议、构建 UI 的基石。

3.1 Python 环境配置与虚拟环境

先说说为什么需要虚拟环境。你想想看,项目 A 用 Django 3.2,项目 B 用 Django 4.0,装在一起肯定打架。虚拟环境就是给每个项目一个独立的 Python 解释器和包空间。

3.1.1 安装 Python 与 pip

我个人习惯用 Python 3.10+,因为类型提示和模式匹配更完善。去官网下载安装包时,记得勾选「Add Python to PATH」。

# 验证安装
python --version
pip --version

小技巧:Windows 用户如果遇到「python 不是内部命令」,多半是 PATH 没配好。我建议直接用 Anaconda 或 Miniconda,省心很多。

3.1.2 创建虚拟环境

Python 自带的 venv 模块就够用。我在项目中通常这样操作:

# 创建虚拟环境(项目根目录下执行)
python -m venv venv

# 激活环境
# Windows:
venv\Scripts\activate
# macOS / Linux:
source venv/bin/activate

# 看到 (venv) 前缀就成功了
(venv) C:\project>

3.1.3 管理依赖

依赖管理是避坑重灾区。我曾经在一个无人机项目中,因为 numpy 版本不一致,导致矩阵运算结果全错,排查了两天。后来我养成了固定版本的习惯:

# 安装包
pip install numpy==1.24.3

# 导出依赖清单
pip freeze > requirements.txt

# 别人拉代码后一键安装
pip install -r requirements.txt
命令 作用 常用场景
python -m venv venv 创建虚拟环境 每个项目开始时执行一次
pip install 安装包 添加新依赖时
pip freeze > requirements.txt 导出依赖 提交代码前更新
pip install -r requirements.txt 批量安装 克隆项目后首次运行

注意:不要把 venv 文件夹提交到 Git!在项目根目录创建 .gitignore 文件,写入 venv/ 和 __pycache__/。

3.2 数据类型与结构

Python 的数据类型,说白了就是用来装数据的容器。航线规划中,经纬度用浮点数,航点列表用列表,无人机状态用字典。选对类型,代码就成功了一半。

3.2.1 基本数据类型

# 数值
altitude = 120.5          # float - 飞行高度(米)
waypoint_count = 8        # int - 航点数量

# 布尔
is_armable = True         # bool - 是否可解锁

# 字符串
drone_id = "DJI-M300-01"  # str - 无人机编号

3.2.2 容器类型

容器类型是 Python 的精华。我经常用列表存航点序列,用字典存无人机参数:

# 列表 - 有序、可修改
waypoints = [
    (22.5431, 114.0579, 50),   # (纬度, 经度, 高度)
    (22.5440, 114.0588, 60),
    (22.5449, 114.0597, 70)
]

# 元组 - 有序、不可修改
home_position = (22.5422, 114.0568, 30)  # 起飞点坐标

# 字典 - 键值对
drone_status = {
    "battery": 85,           # 电量百分比
    "gps_sats": 12,          # GPS 卫星数
    "mode": "GUIDED"         # 飞行模式
}

# 集合 - 无序、不重复
available_modes = {"STABILIZE", "ALT_HOLD", "GUIDED", "AUTO"}
类型 特点 航线规划中的应用
列表 有序、可变 航点序列、日志记录
元组 有序、不可变 坐标点、固定参数
字典 键值映射 无人机状态、配置参数
集合 无序、去重 可用模式、已访问航点

经验之谈:用元组存坐标,因为坐标不应该被修改;用字典存状态,因为字段名就是自文档。我曾经见过有人用列表存无人机状态,结果索引一乱,整个系统都崩了。

3.3 控制流与函数

控制流就是让代码「做决策」和「重复做」。函数则是把逻辑打包,方便复用。航线规划中,判断电量是否足够、循环执行航点任务,都离不开它们。

3.3.1 条件判断

battery = 25

if battery > 50:
    print("电量充足,继续执行任务")
elif battery > 20:
    print("电量偏低,准备返航")
else:
    print("电量危急,立即降落!")

3.3.2 循环

# for 循环 - 遍历航点
for i, wp in enumerate(waypoints):
    print(f"前往航点 {i+1}: {wp}")

# while 循环 - 等待条件满足
while drone_status["battery"] > 20:
    print("正在执行任务...")
    # 模拟电量消耗
    drone_status["battery"] -= 1

3.3.3 函数定义

函数是代码复用的最小单元。我写函数时,一定会加类型注解和文档字符串,这样别人(包括三个月后的自己)一看就懂:

def calculate_distance(lat1: float, lon1: float,
                       lat2: float, lon2: float) -> float:
    """
    计算两个经纬度点之间的距离(Haversine 公式)
    
    Args:
        lat1, lon1: 起点坐标
        lat2, lon2: 终点坐标
    
    Returns:
        距离,单位:米
    """
    import math
    R = 6371000  # 地球半径(米)
    phi1, phi2 = math.radians(lat1), math.radians(lat2)
    dphi = math.radians(lat2 - lat1)
    dlambda = math.radians(lon2 - lon1)
    
    a = (math.sin(dphi/2)**2 +
         math.cos(phi1) * math.cos(phi2) *
         math.sin(dlambda/2)**2)
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
    return R * c

# 调用
dist = calculate_distance(22.5431, 114.0579, 22.5440, 114.0588)
print(f"两点距离: {dist:.2f} 米")

避坑指南:我曾经在函数里直接修改了全局的 waypoints 列表,导致其他模块拿到的数据全乱了。记住:函数参数传递的是引用,修改可变对象会影响外部。要么传副本,要么返回新对象。

3.4 面向对象编程基础

面向对象编程(OOP)就是把数据和操作数据的方法打包成一个「对象」。无人机本身就是一个完美的对象——它有属性(电量、位置),也有行为(起飞、降落)。

3.4.1 类与对象

class Drone:
    """无人机类"""
    
    def __init__(self, drone_id: str, model: str):
        self.drone_id = drone_id
        self.model = model
        self.battery = 100
        self.latitude = 0.0
        self.longitude = 0.0
        self.altitude = 0.0
        self.is_armed = False
    
    def arm(self) -> bool:
        """解锁无人机"""
        if self.battery > 20:
            self.is_armed = True
            print(f"{self.drone_id} 已解锁")
            return True
        else:
            print("电量不足,无法解锁")
            return False
    
    def takeoff(self, target_altitude: float) -> None:
        """起飞到指定高度"""
        if not self.is_armed:
            print("请先解锁")
            return
        self.altitude = target_altitude
        print(f"{self.drone_id} 起飞到 {target_altitude} 米")

# 创建对象
my_drone = Drone("UAV-001", "M300 RTK")
my_drone.arm()          # 解锁
my_drone.takeoff(50)    # 起飞到 50 米

3.4.2 继承与多态

继承可以复用代码。比如我们有固定翼和多旋翼两种无人机,它们都有起飞、降落的行为,但实现方式不同:

class FixedWing(Drone):
    """固定翼无人机"""
    
    def takeoff(self, target_altitude: float) -> None:
        # 固定翼需要滑跑起飞
        print(f"{self.drone_id} 滑跑起飞中...")
        super().takeoff(target_altitude)

class MultiRotor(Drone):
    """多旋翼无人机"""
    
    def takeoff(self, target_altitude: float) -> None:
        # 多旋翼垂直起飞
        print(f"{self.drone_id} 垂直起飞中...")
        super().takeoff(target_altitude)

# 多态:同一个接口,不同实现
drones = [FixedWing("FW-001", "Skywalker"), MultiRotor("MR-001", "M300")]
for d in drones:
    d.takeoff(50)

我的习惯:写类时先想清楚「这个对象有什么属性、能做什么事」。属性用 self. 开头,方法名用动词。好的类设计,读起来就像在描述现实世界。

3.5 本章知识体系

下面这张图把本章的核心知识点串起来了。你可以把它当作一张「地图」,学完后再回来对照,看看自己掌握了多少。

Python 基础回顾(上)知识体系 Python 基础 环境配置 venv 创建与激活 pip 与 requirements.txt 数据类型 int / float / bool / str list / tuple / dict / set 控制流与函数 if / for / while 函数定义与类型注解 面向对象 类与 __init__ 继承与多态 应用:无人机航线规划系统 💡 虚线表示:每个知识点最终都服务于航线规划系统