
本文介绍使用闭包配合 set 实现对 DataFrame 某列中每个单元格的空格分隔字符串进行全局去重(即首次出现保留,后续重复项彻底剔除),适用于清洗含重复邮箱的多行文本数据。
本文介绍使用闭包配合 `set` 实现对 dataframe 某列中每个单元格的空格分隔字符串进行全局去重(即首次出现保留,后续重复项彻底剔除),适用于清洗含重复邮箱的多行文本数据。
在处理批量邮箱数据时,常遇到同一邮箱在不同行、甚至同一行内多次出现的情况。若仅用 str.split().apply(set).str.join() 仅能实现单行内去重,无法满足“跨行首次出现才保留”的业务需求(如构建唯一邮箱列表、避免重复发送)。此时需维护一个全局状态,记录所有已出现过的邮箱,并在遍历每行时动态过滤。
Pandas 的 .apply() 方法天然支持函数式编程,而闭包(closure) 是 Python 中封装可变状态(如 set())并返回定制化处理函数的理想方式。以下为推荐实现:
✅ 核心方案:闭包 + 全局 set 记忆
import pandas as pd
df = pd.DataFrame({
"left": ["One", "Two", "Three"],
"center": [
"email1@email.com email2@email.com email3@email.com",
"email1@email.com email15@email.com email12@email.com",
"email15@email.com email16@email.com"
]
})
def make_email_filter():
seen = set() # 跨行共享的已见邮箱集合
def filter_row(emails_str):
result = []
for email in emails_str.split():
if email not in seen:
seen.add(email)
result.append(email)
return " ".join(result)
return filter_row
# 应用闭包生成的函数(注意:只调用一次 make_email_filter())
df["center"] = df["center"].apply(make_email_filter())
print(df)输出:
left center 0 One email1@email.com email2@email.com email3@email.com 1 Two email15@email.com email12@email.com 2 Three email16@email.com
⚠️ 关键注意事项
-
闭包必须只实例化一次:
df["center"].apply(make_email_filter())中make_email_filter()被调用一次,返回带记忆功能的filter_row;若误写为df["center"].apply(make_email_filter)(无括号)或多次调用闭包,则状态不共享,退化为单行去重。 -
空格是唯一分隔符:本方案默认以空白字符(空格、制表符等)切分。若原始数据含逗号、分号或换行符,请先统一预处理:
df["center"] = df["center"].str.replace(r"[,\n\r;]+", " ", regex=True).str.strip() -
邮箱格式校验建议前置:真实场景中应先清洗非法字符(如 HTML 标签、
<a></a>链接)、标准化格式(统一小写、去除首尾空格),再执行去重:df["center"] = df["center"].str.replace(r"<[^>]+>", " ", regex=True) # 去 HTML df["center"] = df["center"].str.replace(r"\s+", " ", regex=True).str.strip()
? 扩展:支持域名黑名单过滤
如需同时剔除特定域名(如 @spam.com)的邮箱,可增强闭包参数:
def make_email_filter(discard_domains=None):
seen = set()
discard_domains = discard_domains or set()
def filter_row(emails_str):
result = []
for email in emails_str.split():
# 跳过黑名单域名
if any(email.endswith(f"@{dom}") for dom in discard_domains):
continue
if email not in seen:
seen.add(email)
result.append(email)
return " ".join(result)
return filter_row
# 使用示例:过滤 @tempmail.org 和 @invalid.com
df["center"] = df["center"].apply(make_email_filter(discard_domains={"tempmail.org", "invalid.com"}))该方案兼顾可读性、可维护性与性能,适用于万级以内邮箱清洗任务。对于超大规模数据,可考虑改用 itertools.chain + dict.fromkeys() 优化内存,但核心逻辑不变:状态管理靠闭包,去重逻辑靠 set 查找,顺序保留靠遍历过程。

















