点击上方蓝字 关注我们
2026
在上一章《Python学习|RS Python入门02:列表、字典与流程控制:像元集合的编队【三】条件判断(if-elif-else)》中我们提到了条件判断,如果说条件判断是让程序学会了选择,那么循环就是赋予了程序重复的能力。通过循环,我们可以用简洁的代码指挥计算机,对海量数据高效、准确地进行批量操作。
在实际编程中,很多操作并不是只做一次,而是需要对大量数据反复执行同样的步骤。例如,对多景遥感影像进行统一处理、对每一个波段进行计算、对时间序列逐年分析等。如果每一步都手工编写,不仅效率低,而且容易出错。循环结构正是为了解决这类“重复劳动”的问题而存在的。
在 Python 中,最常用的循环结构是 for 循环和 while 循环,其中 for 循环尤其适合处理序列型数据,在遥感数据处理中应用极其广泛。
for 循环:处理序列数据
for 循环是 Python 中处理序列数据的主要工具。所谓“序列”,可以是列表、元组、字符串、字典,甚至是由 range() 生成的数字序列。
for 循环的核心思想是:从一个可迭代对象中依次取出元素,对每个元素执行同样的操作。
在遥感领域中,for 循环常用于以下场景:
对多个影像文件进行批量处理;
遍历多光谱影像的各个波段;
逐像元或逐行列进行计算;
对时间序列数据进行逐期分析。
由于本期较长,在此不再对相关结果进行截图展示。
Part.01
for 循环的基本语法结构
首先来看 for 循环的基本写法。最常见的形式是“遍历一个序列”,即从序列中逐个取出元素。
遍历列表
for item in sequence:process(item) # 对每个item执行操作
在这个结构中,sequence 是一个可迭代对象,item 会依次代表其中的每一个元素,循环体中的代码会对每个元素执行一次。
除了直接遍历序列外,for 循环也常常配合 range() 使用,用于控制循环的次数或索引值。
遍历数字范围
for i in range(start, stop, step):process_with_index(i) # 使用索引i
这种形式在按索引访问数组、栅格行列号、时间步长等场景中非常常见。
Part.02
遍历不同类型的数据结构
Python 的 for 循环不仅可以遍历列表,还可以遍历多种常见的数据结构。
遍历列表(最常用)
列表是遥感处理中最常见的数据容器,例如波段名称列表、影像文件列表等。
bands = ["Blue", "Green", "Red", "NIR"]for band in bands:print(f"处理波段: {band}")
在这个例子中,for 循环会依次取出每一个波段名称,并执行相同的处理逻辑。
遍历元组
元组通常用于存储固定结构的数据,例如坐标值、参数组合等。
coordinates = (120.5, 31.2, 115.8, 39.9)for coord in coordinates:print(f"坐标值: {coord}")
遍历字符串
字符串本质上也是字符序列,因此可以逐字符遍历。
for char in "NDVI":print(f"字符: {char}")
这种方式在解析波段标识、文件名等场景中偶尔会用到。
遍历字典
字典在遥感数据处理中常用于存储元数据、配置参数等。for 循环可以分别遍历字典的键、值或键值对。
metadata = {"sensor": "Landsat8", "date": "2023-06-15", "cloud_cover": 12.5}for key in metadata:print(f"键: {key}")for value in metadata.values():print(f"值: {value}")for key, value in metadata.items():print(f"{key}: {value}")
Part.03
遥感数据处理中的典型应用
1. 批量处理影像文件
在遥感工作中,单景影像处理几乎没有实际意义,更多时候需要对文件夹中的大量影像进行批量处理。for 循环是实现这一过程的核心工具。
import osimage_dir = "/data/landsat/time_series/"image_files = [f for f in os.listdir(image_dir) if f.endswith(".tif")]processed_count = 0for filename in image_files:file_path = os.path.join(image_dir, filename)print(f"正在处理: {filename}")result = process_single_image(file_path)processed_count += 1print(f"成功处理: {filename}")print(f"处理失败 {filename}: {str(e)}")print(f"处理完成,共处理 {processed_count} 景影像")
这个示例展示了 for 循环与异常处理结合使用的典型模式,即在保证程序不中断的前提下,尽可能多地完成数据处理任务。
2. 遍历波段进行计算
多光谱和高光谱影像通常包含多个波段,很多计算都需要对不同波段进行判断、预处理和组合运算。
def calculate_spectral_indices(image_data, band_names):indices = {}for band_name in band_names:if band_name.startswith("B"):print(f"预处理波段: {band_name}")if "Red" in image_data and "NIR" in image_data:red = image_data["Red"]nir = image_data["NIR"]ndvi = (nir - red) / (nir + red + 1e-10)indices["NDVI"] = ndviprint("计算NDVI完成")spectral_indices_to_calculate = [("NDWI", ["Green", "NIR"]),("EVI", ["Blue", "Red", "NIR"]),("SAVI", ["Red", "NIR"])]for index_name, required_bands in spectral_indices_to_calculate:if all(band in image_data for band in required_bands):index_value = calculate_specific_index(index_name, image_data)indices[index_name] = index_valueprint(f"计算{index_name}完成")return indices
3. 时间序列分析
遥感时间序列分析的本质是“按时间顺序逐期处理并比较数据”,for 循环在这一过程中不可或缺。
def analyze_time_series(image_series, dates):results = {"trend": None,"seasonality": [],"anomalies": []}for i, (image, date) in enumerate(zip(image_series, dates)):print(f"分析日期: {date}")current_stats = calculate_image_statistics(image)if i > 0:previous_stats = calculate_image_statistics(image_series[i-1])change = current_stats["mean"] - previous_stats["mean"]if abs(change) > previous_stats["std"] * 2:results["anomalies"].append({"date": date,"change": change,"type": "significant_change"})print(f"检测到异常变化: {date}")if len(image_series) >= 3:results["trend"] = calculate_trend(image_series)print("趋势分析完成")return results
Part.04
range() 函数的应用
在进一步学习 for 循环时,有必要单独介绍一个几乎与 for 循环“绑定使用”的函数——range()。
range() 用于生成一系列连续的整数,它本身并不会一次性生成一个列表,而是返回一个可迭代对象,供 for 循环逐个取值。这种设计在处理大规模数据时非常节省内存。
1. range() 的基本用法
range() 常见有三种使用形式,分别用于不同的循环控制需求。
生成指定范围的数字序列
range(stop) - 0到stop-1
range(start, stop) - start到stop-1
range(start, stop, step) - 指定步长
下面的示例展示了如何利用 range() 构建一个二维网格索引,这在栅格数据处理中非常常见,例如按行列访问像元。
创建10×10的网格索引
rows = 10cols = 10print("网格坐标:")for row in range(rows):for col in range(cols):print(f"({row}, {col})", end=" ")print()
在这个嵌套循环中,外层循环控制行号,内层循环控制列号,最终形成完整的行列遍历结构。
range() 还可以用于反向遍历,这在按波段编号或倒序处理时间序列时非常有用。
反向遍历
print("反向遍历波段:")for i in range(10, 0, -1):print(f"波段 B{i}")
此外,通过设置 step 参数,可以实现按固定间隔采样,例如每隔若干像元取一个值。
遍历特定间隔
print("每隔3个像元采样:")width = 100for x in range(0, width, 3):print(f"采样列: {x}")
2. range() 在遥感数据处理中的典型应用
在处理大规模栅格数据时,往往无法一次性将全部数据载入内存。这时通常采用“分块处理”的策略,而 range() 正是控制分块位置的关键工具。
def process_raster_by_chunks(raster_data, chunk_size=256):rows, cols = raster_data.shapeprocessed_data = np.zeros_like(raster_data)for row_start in range(0, rows, chunk_size):row_end = min(row_start + chunk_size, rows)for col_start in range(0, cols, chunk_size):col_end = min(col_start + chunk_size, cols)chunk = raster_data[row_start:row_end, col_start:col_end]processed_chunk = process_chunk(chunk)processed_data[row_start:row_end, col_start:col_end] = processed_chunkprint(f"处理块: ({row_start}:{row_end}, {col_start}:{col_end})")return processed_data
另一个常见需求是根据影像的地理参考信息,生成每一个像元对应的真实地理坐标。
def generate_pixel_coordinates(width, height, geotransform):coordinates = []# geotransform = (左上角x, 像元宽度, 旋转, 左上角y, 旋转, 像元高度)x_origin, pixel_width, _, y_origin, _, pixel_height = geotransformfor row in range(height):for col in range(width):x = x_origin + col * pixel_width + pixel_width / 2y = y_origin + row * pixel_height + pixel_height / 2coordinates.append((x, y))return coordinates
Part.05
enumerate() 函数的应用
在很多循环场景中,我们不仅需要“值”,还需要“索引”。虽然可以通过 range(len(sequence)) 的方式实现,但这种写法不够直观,也容易出错。enumerate() 函数正是为了解决这一问题而设计的。
1. 同时获取索引和值
enumerate() 会在遍历序列时,自动返回一个“索引 + 元素值”的组合。
基本用法
bands = ["Blue", "Green", "Red", "NIR", "SWIR1", "SWIR2"]print("波段列表:")for index, band in enumerate(bands):print(f"索引 {index}: {band}")
enumerate() 还可以通过 start 参数指定索引的起始值,这在按“第 1 个波段、第 2 个波段”这类习惯编号时非常方便。
指定起始索引
print("\n从1开始计数:")for index, band in enumerate(bands, start=1):print(f"波段 {index}: {band}")
在实际遥感处理中,经常需要对多个波段依次执行相同的归一化、滤波或统计操作。
def normalize_bands(band_data_list):normalized_bands = []for i, band_data in enumerate(band_data_list):print(f"正在归一化波段 {i+1}")min_val = band_data.min()max_val = band_data.max()normalized = (band_data - min_val) / (max_val - min_val)normalized_bands.append(normalized)print(f"波段 {i+1}: 范围 [{min_val:.3f}, {max_val:.3f}]")return normalized_bands
2. 遍历复杂数据结构
enumerate() 不仅可以用于列表,也可以与字典的 items() 方法结合,用于同时获取处理顺序和具体内容。
def process_multispectral_image(image_bands, metadata):results = {}for idx, (band_name, band_data) in enumerate(image_bands.items()):print(f"处理第 {idx+1}/{len(image_bands)} 个波段: {band_name}")band_metadata = metadata.get("bands", {}).get(band_name, {})if band_metadata.get("requires_radiometric_correction", False):print(f" 对 {band_name} 进行辐射校正")band_data = apply_radiometric_correction(band_data, band_metadata)if band_metadata.get("requires_atmospheric_correction", False):print(f" 对 {band_name} 进行大气校正")band_data = apply_atmospheric_correction(band_data, band_metadata)results[band_name] = band_datareturn results
Part.06
zip() 函数的应用
在很多情况下,我们需要“并行遍历”多个序列,例如波段名、波长和空间分辨率一一对应。zip() 函数可以将多个序列打包在一起,使它们在 for 循环中同步前进。
1. 并行遍历多个序列
基本用法:同时遍历多个列表
band_names = ["B2", "B3", "B4", "B8"]wavelengths = [490, 560, 665, 842]resolutions = [10, 10, 10, 10]print("波段详细信息:")for name, wavelength, resolution in zip(band_names, wavelengths, resolutions):print(f"{name}: {wavelength}nm, {resolution}m")
需要注意的是,zip() 会以最短序列为准停止遍历。
from itertools import zip_longestshort_list = ["B1", "B2", "B3"]long_list = [443, 490, 560, 665, 842]print("\n使用zip:")for a, b in zip(short_list, long_list):print(f"{a}: {b}")print("\n使用zip_longest:")for a, b in zip_longest(short_list, long_list, fillvalue="N/A"):print(f"{a}: {b}")
2. zip() 在遥感中的应用
zip() 在波段组合、时间序列对齐、质量筛选等任务中非常常见。
def calculate_band_ratios(band_data_dict):ratios = {}band_names = list(band_data_dict.keys())for i, band1 in enumerate(band_names):for j, band2 in enumerate(band_names):if i >= j:continueratio_name = f"{band1}_{band2}_ratio"data1 = band_data_dict[band1]data2 = band_data_dict[band2]with np.errstate(divide='ignore', invalid='ignore'):ratio = np.where(data2 != 0, data1 / data2, 0)ratios[ratio_name] = ratioreturn ratiosdef align_time_series(images, dates, quality_scores):aligned_data = []for image, date, quality in zip(images, dates, quality_scores):if quality >= 0.8:aligned_data.append({"date": date,"image": image,"quality": quality})else:print(f"跳过低质量影像: {date}")return aligned_data
Part.07
列表推导式与 for 循环
在前面的内容中,我们已经看到,for 循环可以完成几乎所有的遍历与处理任务。但在很多情况下,传统 for 循环的写法会显得冗长。
Python 提供了“推导式”这一更简洁的写法,用于在一行代码中完成“遍历 + 条件判断 + 结果生成”的过程。
需要强调的是,列表推导式并不是 for 循环的替代,而是 for 循环的一种简化形式。理解了 for 循环之后,再学习推导式会非常自然。
1. 基本列表推导式
先对比传统 for 循环与列表推导式的写法差异。
传统for循环
squares = []for i in range(1, 11):squares.append(i ** 2)
列表推导式(更简洁)
squares = [i ** 2 for i in range(1, 11)]可以看到,列表推导式将“循环、计算、添加结果”这三步合并到了一行中,逻辑更加紧凑。
在遥感数据处理中,列表推导式常用于批量生成文件名、路径或参数组合。
遥感应用:批量生成文件名
years = [2020, 2021, 2022, 2023]months = ["01", "06"]# 生成所有年月组合的文件名file_names = [f"LC08_{year}{month}01.tif"for year in yearsfor month in months]print("生成的文件名:", file_names[:5])
这种写法在构建时间序列影像路径时非常高效,也能减少不必要的中间变量。
2. 带条件的列表推导式
列表推导式不仅可以遍历,还可以在生成结果时加入条件判断,用于筛选数据。
筛选特定波段
all_bands = ["B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B8A", "B9", "B10", "B11", "B12"]visible_bands = [band for band in all_bandsif band in ["B2", "B3", "B4"]]print("可见光波段:", visible_bands)
在这个例子中,列表推导式清晰地表达了“从所有波段中筛选出可见光波段”的逻辑。
类似地,也可以用于筛选满足特定条件的影像数据。
筛选满足条件的影像
images = [{"name": "img1", "cloud_cover": 5.2, "resolution": 30},{"name": "img2", "cloud_cover": 35.1, "resolution": 30},{"name": "img3", "cloud_cover": 12.3, "resolution": 10},{"name": "img4", "cloud_cover": 8.7, "resolution": 30}]# 筛选低云量、高分辨率的影像selected_images = [img for img in imagesif img["cloud_cover"] < 20 and img["resolution"] <= 30]print("选中的影像:", [img["name"] for img in selected_images])
3. 嵌套列表推导式
当处理二维或三维数据结构时,列表推导式也可以进行嵌套使用,但需要注意可读性。
创建二维数组(模拟小栅格)
rows, cols = 3, 4使用嵌套循环
grid = []for i in range(rows):row = []for j in range(cols):row.append(i * cols + j)grid.append(row)
使用嵌套列表推导式
grid = [[i * cols + j for j in range(cols)] for i in range(rows)]print("栅格数据:")for row in grid:print(row)
在遥感中,嵌套列表推导式常用于快速提取多维数组中的所有像元值。
def extract_all_pixel_values(raster_data):all_values = [raster_data[b, r, c]for b in range(raster_data.shape[0])for r in range(raster_data.shape[1])for c in range(raster_data.shape[2])]return all_values
不过需要注意,这类操作在数据量很大时容易造成内存压力,实际工程中应谨慎使用。
Part.08
字典推导式与集合推导式
除了列表推导式,Python 还提供了字典推导式和集合推导式,用于快速构建字典和集合结构。
1. 字典推导式
字典推导式常用于建立“键–值”映射关系,例如波段编码与波长之间的对应关系。
创建波段名到波长的映射
band_codes = ["B01", "B02", "B03", "B04"]wavelengths = [443, 490, 560, 665]band_to_wavelength = {band: wavelengthfor band, wavelength in zip(band_codes, wavelengths)}print("波段-波长映射:", band_to_wavelength)
也可以结合 enumerate() 快速构建带编号的字典。
bands = ["Coastal_Aerosol", "Blue", "Green", "Red", "NIR"]band_dict = {band: f"Band_{i+1}" for i, band in enumerate(bands)}print("波段字典:", band_dict)
2. 集合推导式
集合推导式主要用于去重和提取唯一值,这在整理影像元数据时非常实用。
提取所有唯一的传感器类型
sensor_records = [{"sensor": "Landsat8", "date": "2023-01-01"},{"sensor": "Sentinel2", "date": "2023-01-02"},{"sensor": "Landsat8", "date": "2023-01-03"},{"sensor": "MODIS", "date": "2023-01-04"},{"sensor": "Sentinel2", "date": "2023-01-05"}]unique_sensors = {record["sensor"] for record in sensor_records}print("唯一传感器类型:", unique_sensors)
集合还可以用于集合运算,例如查找多个影像中共同存在的波段。
找出出现在多景影像中的波段
band_sets = [{"B1", "B2", "B3", "B4"},{"B2", "B3", "B4", "B5"},{"B3", "B4", "B5", "B8"},{"B4", "B5", "B8", "B11"}]common_bands = set.intersection(*band_sets)print("共同波段:", common_bands)
Part.09
高级 for 循环技巧
随着数据规模的增大和处理逻辑的复杂化,简单的 for 循环有时已经难以满足需求。Python 提供了一些专门用于增强循环能力的工具和技术,可以在保证代码清晰性的同时,提高表达能力和执行效率。
1. 使用 itertools 模块
itertools 模块提供了一系列用于高效迭代的工具,特别适合用于组合、排列、参数穷举等场景,在遥感参数组合测试和波段组合分析中非常常见。
from itertools import product, combinations, permutations# product 用于生成多个序列的笛卡尔积,常用于枚举所有可能的处理参数组合。resolutions = [10, 20, 30]compression_methods = ["LZW", "DEFLATE", "JPEG"]print("所有可能的处理组合:")for res, comp in product(resolutions, compression_methods):print(f"分辨率: {res}m, 压缩: {comp}")
在这个例子中,for 循环会遍历所有“分辨率 × 压缩方式”的组合,这在影像批处理参数测试中非常实用。
combinations 用于生成不考虑顺序的组合,常用于波段组合分析。
bands = ["B1", "B2", "B3", "B4"]print("\n所有可能的波段组合(3个一组):")for combo in combinations(bands, 3):print(combo)
同样的方法也可以用于生成所有可能的波段比值组合。
print("\n所有可能的波段比值:")for band1, band2 in combinations(bands, 2):print(f"{band1}/{band2}")
2. 生成器表达式(内存友好)
在处理大规模遥感数据时,内存往往是限制因素之一。生成器的特点是“按需生成”,不会一次性将所有结果加载到内存中,非常适合处理大文件或长时间序列。
def process_large_image_files(file_list):for file_path in file_list:print(f"正在读取: {file_path}")for chunk in read_image_in_chunks(file_path):processed_chunk = process_chunk(chunk)yield processed_chunk
在使用生成器时,for 循环会逐个获取生成的结果,而不是一次性加载所有数据。
large_files = ["big_image1.tif", "big_image2.tif", "big_image3.tif"]for processed_chunk in process_large_image_files(large_files):save_chunk(processed_chunk)
生成器表达式是生成器的一种简写形式,适合逻辑简单但数据量巨大的场景。
ndvi_values = (calculate_ndvi(pixel) for pixel in large_pixel_stream)for ndvi in ndvi_values:if ndvi > 0.6:classify_vegetation(ndvi)
3. 异步 for 循环
当处理过程涉及大量 I/O 操作(例如文件读取、网络请求)时,异步循环可以显著提升程序整体效率。
import asyncioasync def process_multiple_images_async(image_paths):tasks = []for path in image_paths:task = asyncio.create_task(process_single_image_async(path))tasks.append(task)results = await asyncio.gather(*tasks, return_exceptions=True)successful = 0for i, result in enumerate(results):if isinstance(result, Exception):print(f"处理失败 {image_paths[i]}: {result}")else:successful += 1print(f"成功处理 {successful}/{len(image_paths)} 个文件")return resultsasync def process_single_image_async(file_path):await asyncio.sleep(0.1)return {"file": file_path, "status": "processed"}
异步 for 循环在批量下载、云端数据读取、API 请求等现代遥感应用中越来越常见。
Part.10
性能优化与最佳实践
在遥感数据处理中,for 循环往往运行在大量数据之上,稍有不慎就可能导致程序运行缓慢。因此,在保证结果正确的前提下,合理优化循环结构非常重要。
1. 避免在循环内重复计算
在循环中反复执行相同的计算是常见的低效写法。
def process_bands_slow(band_data):results = []for band in band_data:iflen(band_data) > 5:result = expensive_computation(band)else:result = simple_computation(band)results.append(result)return results
改进的方法是将不变的计算提前到循环外部。
def process_bands_fast(band_data):results = []band_count = len(band_data)if band_count > 5:process_func = expensive_computationelse:process_func = simple_computationfor band in band_data:results.append(process_func(band))return results
2. 使用局部变量加速
在 Python 中,局部变量的访问速度通常快于全局变量。在大量循环中,这种差异会被放大。
def calculate_statistics_fast(data):sum_func = sumlen_func = lenappend_func = results.appendresults = []for subset in data:mean_val = sum_func(subset) / len_func(subset)append_func(mean_val)return results
3. 循环的向量化替代
对于数值型数据,尤其是栅格数据,优先考虑使用 NumPy 的向量化操作,往往可以获得数量级上的性能提升。
import numpy as npdef normalize_bands_loop(band_list):normalized = []for band in band_list:min_val = np.min(band)max_val = np.max(band)norm_band = (band - min_val) / (max_val - min_val)normalized.append(norm_band)return normalizeddef normalize_bands_vectorized(band_list):bands_array = np.array(band_list)min_vals = np.min(bands_array, axis=(1, 2), keepdims=True)max_vals = np.max(bands_array, axis=(1, 2), keepdims=True)normalized = (bands_array - min_vals) / (max_vals - min_vals)return list(normalized)
通过简单的时间对比,可以直观感受到性能差异。
import timetest_bands = [np.random.rand(1000, 1000) for _ in range(10)]start = time.time()result_loop = normalize_bands_loop(test_bands)print(f"循环版本耗时: {time.time() - start:.4f}秒")start = time.time()result_vec = normalize_bands_vectorized(test_bands)print(f"向量化版本耗时: {time.time() - start:.4f}秒")
Part.11
总结要点
本章围绕 for 循环展开,系统介绍了 Python 中与循环相关的核心工具及其在遥感数据处理中的典型应用,主要包括以下几个方面。
for 循环是 Python 中处理序列数据的基础结构,是遥感编程中最常用、最重要的控制语句之一。
range() 函数用于生成数字序列,是控制循环次数、索引和分块处理的关键工具。
enumerate() 可以同时获取索引和值,使带编号的遍历更加直观、安全。
zip() 支持多个序列的并行遍历,是处理波段、时间序列和质量控制数据的重要手段。
列表推导式、字典推导式和集合推导式提供了更简洁的数据构建方式,但在大数据场景下需注意内存消耗。
itertools 模块扩展了 for 循环的组合能力,适合参数穷举和波段组合分析。
生成器和异步循环为处理大规模遥感数据和 I/O 密集型任务提供了高效解决方案。
在性能优化方面,应避免循环内重复计算,合理使用局部变量,并优先考虑向量化替代方案。
总体而言,for 循环贯穿了遥感数据处理的全部流程。从简单的文件批量处理,到复杂的像元级计算与时间序列分析,掌握 for 循环及其相关技巧,是进行高效、规范、可扩展遥感编程的基础能力。
如果你觉得“原来Python也能讲得这么遥感”,点个关注,后续系列推文将第一时间推送:
结构、控制流、函数、面向对象……
每一步都用卫星影像、波段、像元做例子,拒绝枯燥抽象。
附赠可运行的 Notebook 源码,自行上传即可上手。本公众号后台回复【RS Python入门02-4】即可获取本节 Notebook 源码。