如何实现可接收调用者传入tokenizer参数的Python装饰器?
解决方案:支持动态传入Tokenizer的装饰器
你已经摸到了核心思路,现在的问题就是让装饰器既能封装分词/解码逻辑,又允许调用时灵活替换tokenizer,而不是装饰时就把它固定死。下面给你一个更完善的方案,兼顾灵活性、兼容性和代码简洁性:
先梳理核心需求
- 被装饰的函数只需要负责处理token列表,不用管分词和解码
- 支持在装饰时设置默认tokenizer,也能在调用时传入自定义tokenizer
- 同时兼容你定义的
Tokenizer抽象基类,以及原始的函数式tokenizer(比如str.split)
完整实现代码
首先保留你定义的Tokenizer抽象类和具体实现,再加上一个适配器兼容函数式tokenizer,最后写核心装饰器:
import random import string from abc import ABC, abstractmethod from typing import List, Callable, Any # 你的Tokenizer抽象基类(保留原有逻辑) class Tokenizer(ABC): """ Base class for Tokenizer which provides the encode and decode methods """ def __init__(self, tokenizer: Any) -> None: self.tokenizer = tokenizer @abstractmethod def encode(self, text: str) -> List[str]: """ Tokenize a string into list of strings """ @abstractmethod def decode(self, token_list : List[str]) -> str: """ Creates a string from a tokens list using the tokenizer """ def encode_many(self, texts: List[str]) -> List[List[str]]: return [self.encode(text) for text in texts] def decode_many(self, token_lists: List[List[str]]) -> List[str]: return [self.decode(token_list) for token_list in token_lists] # 你的SingleSpaceTokenizer实现 class SingleSpaceTokenizer(Tokenizer): """ Simple tokenizer that splits on single spaces """ def __init__(self, tokenizer=None) -> None: super().__init__(tokenizer) def encode(self, text: str) -> List[str]: return text.split() def decode(self, token_list: List[str]) -> str: return ' '.join(token_list) # 你的AtTokenizer实现 class AtTokenizer(Tokenizer): def __init__(self, tokenizer=None): super().__init__(tokenizer) def encode(self, text): return text.split('@') def decode(self, token_list): return '@'.join(token_list) # 适配器:把普通Callable(比如str.split)转换成Tokenizer类实例 class CallableTokenizer(Tokenizer): def __init__(self, tokenizer_func: Callable[[str], List[str]], separator: str = ' '): super().__init__(tokenizer_func) self.separator = separator def encode(self, text: str) -> List[str]: return self.tokenizer(text) def decode(self, token_list: List[str]) -> str: return self.separator.join(token_list) # 核心装饰器 def tokenize(default_tokenizer=None): # 处理默认值:如果没传,默认用str.split对应的Tokenizer if default_tokenizer is None: default_tokenizer = CallableTokenizer(str.split) # 如果传入的是普通函数,自动转换成CallableTokenizer elif isinstance(default_tokenizer, Callable): default_tokenizer = CallableTokenizer(default_tokenizer) def decorator(f): def wrapper(text: str, tokenizer=None): # 调用时优先用传入的tokenizer,否则用装饰时的默认值 current_tokenizer = tokenizer or default_tokenizer # 封装分词→处理token→解码的流程 tokens = current_tokenizer.encode(text) processed_tokens = f(tokens) return current_tokenizer.decode(processed_tokens) return wrapper return decorator
多种场景下的使用示例
1. 使用默认tokenizer(str.split)
@tokenize() def change_first_letter(token_list): return [random.choice(string.ascii_letters) + token[1:] for token in token_list] @tokenize() def spellcheck(token_list): from spellchecker import SpellChecker checker = SpellChecker() return [checker.correction(word) for word in token_list] # 直接调用,用默认分词器 print(change_first_letter("hello world")) print(spellcheck("helo wrld"))
2. 装饰时指定默认Tokenizer,调用时替换
# 装饰时默认用SingleSpaceTokenizer @tokenize(SingleSpaceTokenizer()) def change_first_letter(token_list): return [random.choice(string.ascii_letters) + token[1:] for token in token_list] # 调用时换成AtTokenizer new_tokenizer = AtTokenizer() print(change_first_letter("a@b@c", tokenizer=new_tokenizer))
3. 兼容原始函数式tokenizer
# 装饰时默认用按'-'分割的函数 @tokenize(lambda x: x.split('-')) def change_first_letter(token_list): return [random.choice(string.ascii_letters) + token[1:] for token in token_list] print(change_first_letter("test-hello-world"))
这个方案的优势
- 完全满足你的需求:既封装了重复的分词/解码逻辑,又支持调用时动态传入自定义tokenizer
- 兼容性强:同时支持你定义的
Tokenizer类和普通函数式tokenizer - 职责清晰:被装饰的函数只需要专注于token列表的处理,代码更简洁易维护
- 扩展性好:后续新增Tokenizer实现,直接继承抽象基类即可,不需要修改装饰器
内容的提问来源于stack exchange,提问作者shaun
相关产品推荐
相关产品推荐

