1. DataType 是什么:逻辑类型,不等于物理存储类型
DataType 描述的是 表生态里一个值的逻辑类型(Logical Type) ,比如 BIGINT、VARCHAR、DECIMAL(10,2)、ROW<...>。
关键理解点:
- DataType 只定义"是什么类型",不暗示它在网络传输或存储时怎么编码(那是物理表示层的事情)。
- PyFlink 中所有预定义类型都在
pyflink.table.types,并且推荐用DataTypes工具类来构造。
典型写法:
python
from pyflink.table.types import DataTypes
DataTypes.BIGINT()
DataTypes.STRING()
DataTypes.ROW([DataTypes.FIELD("id", DataTypes.BIGINT()),
DataTypes.FIELD("name", DataTypes.STRING())])
在写 UDF 时,你常会看到两种声明方式:
- 用字符串:
result_type='ROW<id BIGINT, data STRING>' - 用 DataTypes:
result_type=DataTypes.ROW([...])
两者都行;工程里建议 DataTypes 写法更安全(IDE 友好,拼写不易错)。
2. DataType 与 Python 类型映射:UDF 参数/返回值怎么落地
当你在 UDF 声明了 DataType,Flink 会做两件事:
1)把输入列值转换成对应的 Python 对象传给你
2)要求你的返回值类型匹配你声明的 DataType(否则运行时会报类型/序列化问题)
你给的映射表可以直接当"速查表"。
2.1 标量 UDF:DataType → Python Type
| Data Type | Python Type |
|---|---|
| BOOLEAN | bool |
| TINYINT / SMALLINT / INT / BIGINT | int |
| FLOAT / DOUBLE | float |
| VARCHAR | str |
| VARBINARY | bytes |
| DECIMAL | decimal.Decimal |
| DATE | datetime.date |
| TIME | datetime.time |
| TimestampType | datetime.datetime |
| LocalZonedTimestampType | datetime.datetime |
| INTERVAL YEAR TO MONTH | int(注意:pandas 不支持) |
| INTERVAL DAY TO SECOND | datetime.timedelta(注意:pandas 不支持) |
| ARRAY | list |
| MULTISET | list(Not Supported Yet in pandas) |
| MAP | dict(Not Supported Yet in pandas) |
| ROW | pyflink.common.Row |
2.2 向量化 pandas UDF:输入/输出是 pandas.Series(元素类型由 DataType 决定)
文档里的核心点是:
对于 vectorized Python UDF,输入类型和输出类型是
pandas.Series,Series 里每个元素类型对应你声明的 DataType。
映射中 pandas type 这一列很关键:
| Data Type | Pandas Type |
|---|---|
| BOOLEAN | numpy.bool_ |
| INT | numpy.int32 |
| BIGINT | numpy.int64 |
| FLOAT | numpy.float32 |
| DOUBLE | numpy.float64 |
| VARCHAR | str |
| VARBINARY | bytes |
| DECIMAL | decimal.Decimal |
| DATE / TIME / Timestamp | datetime.* |
| ARRAY | numpy.ndarray |
| ROW | dict |
注意:表里也写了很多 Not Supported Yet ,比如 MAP/MULTISET/INTERVAL 在 pandas UDF 场景还不支持或不完整,生产里尽量绕开或提前验证。
3. 复合类型在 UDF 中的使用:ARRAY / MAP / ROW
这是工程里最容易踩坑的部分:复合类型传到 Python 侧会变成什么?
3.1 ARRAY:Python 侧是 list(pandas 侧是 ndarray)
- 标量 UDF:拿到的是
list - pandas UDF:单元格可能是
numpy.ndarray
示例(标量 UDF):
python
from pyflink.table.udf import udf
from pyflink.table.types import DataTypes
@udf(
input_types=[DataTypes.ARRAY(DataTypes.INT())],
result_type=DataTypes.INT()
)
def array_len(arr):
return len(arr) if arr is not None else 0
3.2 MAP:Python 侧是 dict(pandas UDF 暂不支持)
MAP 在 Python 标量 UDF 中是 dict,但 pandas UDF 不支持(表里明确写了 Not Supported Yet),如果你需要向量化处理 MAP,通常要先在 SQL/Table 层把 MAP 展开/转换成基础列。
3.3 ROW:Python 侧是 Row(pandas UDF 侧是 dict)
- 标量 UDF:ROW →
pyflink.common.Row - pandas UDF:ROW →
dict
这也是为什么你在 Row-based Operations 里看到两种写法:
def func2(r: Row) -> Row: ...- pandas 模式下
DataFrame里取列,再拼DataFrame返回
4. 为什么"声明类型"很重要:类型决定运行时转换与序列化
在 PyFlink 里,很多问题的根源都是"类型不明确":
- 你写 UDF 返回了 Python
int,但声明的是STRING - 你返回 Row 的字段数量/顺序与声明的
ROW<...>不一致 - pandas UDF 返回列的 dtype 跟 DataType 冲突
- 你用了 pandas UDF 但传了 MAP/INTERVAL 等 pandas 不支持类型
最稳的做法是:
- UDF 明确写
input_types/result_type(不要只靠推断) - 复合类型尽量用
DataTypes.ROW/ARRAY/...明确结构 - pandas UDF 场景优先用基础数值/字符串/时间类型
5. 给你一套"UDF 类型声明模板"(生产更稳)
5.1 标量 UDF:建议用 DataTypes 写清楚
python
from pyflink.table.udf import udf
from pyflink.table.types import DataTypes
@udf(
input_types=[DataTypes.BIGINT(), DataTypes.STRING()],
result_type=DataTypes.ROW([
DataTypes.FIELD("id", DataTypes.BIGINT()),
DataTypes.FIELD("data", DataTypes.STRING())
])
)
def enrich(id_, data):
from pyflink.common import Row
return Row(id_, data + "_x")
5.2 pandas UDF:牢记输入输出是 Series/DataFrame(元素类型由 DataType 控制)
python
import pandas as pd
from pyflink.table.udf import udf
from pyflink.table.types import DataTypes
@udf(
result_type=DataTypes.ROW([
DataTypes.FIELD("id", DataTypes.BIGINT()),
DataTypes.FIELD("data", DataTypes.STRING())
]),
func_type="pandas"
)
def enrich_vec(df: pd.DataFrame) -> pd.DataFrame:
return pd.concat([df["id"], df["data"] + "_x"], axis=1)
如果你接下来要写 CSDN 系列文章,我建议你把这一篇作为"类型基础篇",下一篇可以直接承接你前面写的 Row-based Operations:把每种算子(map/flat_map/aggregate/flat_aggregate)里涉及到的 result_type、input_types、ROW 扁平化、pandas 模式的 dtype 坑,都用本文的类型映射做解释,会非常连贯。