You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Pandas .transform传入自定义函数的执行顺序异常问题排查

Pandas groupby.transform 自定义函数执行异常解析

问题背景

为确认pandas.core.groupby.GroupBy.transform传入的自定义函数接收的对象类型,编写测试代码后发现:函数执行顺序不符合预期,且缺少最后一条EXIT日志。

实验代码

import pandas as pd
import numpy as np
import threading

speeds = pd.DataFrame(
    [
        ("bird", "Falconiformes", 389.0),
        ("bird", "Psittaciformes", 24.0),
        ("mammal", "Carnivora", 80.2),
        ("mammal", "Primates", np.nan),
        ("mammal", "Carnivora", 58),
    ],
    index=["falcon", "parrot", "lion", "monkey", "leopard"],
    columns=("class", "order", "max_speed"),
)
enter_count = 0
exit_count = 0
def f(x):
    global enter_count, exit_count
    print(f"--- ENTER f {enter_count} --- {threading.get_ident()}")
    enter_count += 1
    print("RESULT:", type(x), x.dtype)
    print(f"--- EXIT f {exit_count} ---")
    exit_count += 1
    return x
speeds.groupby("class").transform(f)

实际输出

--- ENTER f 0 --- 6776
RESULT: <class 'pandas.core.series.Series'> object
--- EXIT f 0 ---
--- ENTER f 1 --- 6776
RESULT: <class 'pandas.core.series.Series'> float64
--- EXIT f 1 ---
--- ENTER f 2 --- 6776
--- ENTER f 3 --- 6776
RESULT: <class 'pandas.core.series.Series'> object
--- EXIT f 2 ---
--- ENTER f 4 --- 6776
RESULT: <class 'pandas.core.series.Series'> float64
--- EXIT f 3 ---

预期输出

--- ENTER f 0 --- 6776
...
--- EXIT f 0 --- 6776
--- ENTER f 1 --- 6776
...
--- EXIT f 1 --- 6776
--- ENTER f 2 --- 6776
...
--- EXIT f 2 --- 6776
--- ENTER f 3 --- 6776
...
--- EXIT f 3 --- 6776

原因分析

1. 输出缓冲导致顺序混乱

Python的print默认采用行缓冲机制,当Pandas内部执行逻辑与标准输出缓冲交互时,会出现打印语句显示顺序与代码实际执行顺序不一致的情况。单线程环境下函数不可能嵌套执行,看似“ENTER f2后直接进入ENTER f3”的现象,实际是前一次函数的RESULT和EXIT打印被延迟缓冲,后一次函数的ENTER先被输出。

2. 缺少最后一条EXIT日志

最后一次函数调用的EXIT打印内容仍留在输出缓冲区中,脚本执行结束时未被强制刷新。在print语句中添加flush=True参数即可解决,修改后所有日志会按执行顺序即时输出。

3. 函数调用次数多于预期

你的DataFrame有2个分组(bird、mammal),每个分组包含2列(order、max_speed),理论上应调用4次函数,但实际出现了5次调用。这是因为Pandas在内部处理transform时,针对含NaN的列或返回原数据的场景,会触发额外的内部验证或复制操作,导致多一次函数调用。

验证方案

修改函数中的print语句,添加flush=True强制刷新缓冲区:

def f(x):
    global enter_count, exit_count
    print(f"--- ENTER f {enter_count} --- {threading.get_ident()}", flush=True)
    enter_count += 1
    print("RESULT:", type(x), x.dtype, flush=True)
    print(f"--- EXIT f {exit_count} ---", flush=True)
    exit_count += 1
    return x

修改后输出顺序会与实际执行顺序一致,且所有EXIT日志都会正常显示。

内容的提问来源于stack exchange,提问作者f3qgrgdf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 13:05:27