子类继承时如何自动生成与自定义正则匹配的ClassTuple?
自动为子类生成匹配正则分组的ClassTuple
问题核心在于:基类的ClassTuple是在基类定义阶段生成的,子类继承时会直接复用基类的ClassTuple,不会自动根据子类的新PATTERN更新,导致分组不匹配的错误。以下是两种简洁的解决方案,无需子类重复编写ClassTuple代码:
方案一:使用__init_subclass__(推荐,Python3.6+)
利用Python3.6引入的__init_subclass__方法,在子类定义时自动生成对应正则分组的ClassTuple,代码直观且无需元类:
import re from collections import namedtuple from typing import Optional class MyBaseClass: PATTERN = re.compile('(?P<digits>[0-9]+).*(?P<suffix>foo|bar)') def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) # 基于子类的PATTERN自动生成ClassTuple cls.ClassTuple = namedtuple('ClassTuple', tuple(cls.PATTERN.groupindex)) def __init__(self, string: Optional[str] = None, tup: Optional["ClassTuple"] = None): if string is not None: mapping = self.PATTERN.fullmatch(string).groupdict() elif tup is not None: mapping = tup._asdict() for k, v in mapping.items(): setattr(self, k, v) # 子类仅需定义PATTERN class MySubClass(MyBaseClass): PATTERN = re.compile('(?P<letters>[A-Za-z]+).*(?P<suffix>fisk|mask)') # 测试代码 # 基类测试 bc = MyBaseClass('123_abc_foo') print(bc.digits, bc.suffix) bc_tuple = MyBaseClass.ClassTuple(digits='456', suffix='bar') bc = MyBaseClass(tup=bc_tuple) print(bc.digits, bc.suffix) # 子类测试 sc = MySubClass('abc_123_fisk') print(sc.letters, sc.suffix) sc_tuple = MySubClass.ClassTuple(letters='def', suffix='mask') sc = MySubClass(tup=sc_tuple) print(sc.letters, sc.suffix)
运行结果:
123 foo 456 bar abc fisk def mask
原理说明
__init_subclass__会在每个子类定义完成后自动触发,此时可以直接访问子类的PATTERN属性,根据其分组名动态生成对应的namedtuple并绑定到子类的ClassTuple上。如果子类未定义PATTERN,则会继承基类的PATTERN并生成对应ClassTuple,保持基类行为一致。
方案二:使用元类(兼容旧版本Python)
如果需要兼容Python3.6之前的版本,可以用元类实现相同逻辑:
import re from collections import namedtuple from typing import Optional class PatternTupleMeta(type): def __new__(cls, name, bases, attrs): new_cls = super().__new__(cls, name, bases, attrs) # 获取子类或继承的PATTERN pattern = attrs.get('PATTERN', getattr(new_cls, 'PATTERN', None)) if pattern: new_cls.ClassTuple = namedtuple('ClassTuple', tuple(pattern.groupindex)) return new_cls class MyBaseClass(metaclass=PatternTupleMeta): PATTERN = re.compile('(?P<digits>[0-9]+).*(?P<suffix>foo|bar)') def __init__(self, string: Optional[str] = None, tup: Optional["ClassTuple"] = None): if string is not None: mapping = self.PATTERN.fullmatch(string).groupdict() elif tup is not None: mapping = tup._asdict() for k, v in mapping.items(): setattr(self, k, v) # 子类使用方式不变 class MySubClass(MyBaseClass): PATTERN = re.compile('(?P<letters>[A-Za-z]+).*(?P<suffix>fisk|mask)')
原理说明
元类的__new__方法会在类创建时被调用,我们可以在此阶段动态修改类的属性,为子类生成对应的ClassTuple。元类的理解成本稍高,但兼容性更好。
适配实际场景
如果你的基类还有其他依赖ClassTuple的代码,只要这些代码是在子类定义完成后(比如实例化时)调用,都会自动使用子类生成的ClassTuple,无需额外修改。
内容的提问来源于stack exchange,提问作者Stalpotaten
相关产品推荐
相关产品推荐

