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