Python重写variab类的初始化

2024-04-27 18:02:47 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个超类和一个子类,需要根据正则表达式以不同的方式处理它们的初始化。参见下面的工作示例。你知道吗

import os
import re


class Sample:
    RE = r'(?P<id>\d+)'
    STRICT_MATCHING = False

    def __init__(self, f):
        self.file = f
        self.basename = os.path.basename(os.path.splitext(self.file)[0])

        re_ = re.compile(self.RE)
        match = re_.fullmatch if self.STRICT_MATCHING else re_.match
        self.__dict__.update(match(self.basename).groupdict())


class DetailedSample(Sample):
    RE = r'(?P<id>\d+)_(?P<dir>[lr])_(?P<n>\d+)'
    STRICT_MATCHING = True


s1 = Sample("/asdf/2.jpg")
print(s1.id)
s2 = DetailedSample("/asdfadsf/2_l_2.jpg")
print(s2.id, s2.dir, s2.n)

此代码可以工作,但有两个缺点:

  • 每次初始化新的Sample时,都会重新编译正则表达式。你知道吗
  • 无法从Sample中的其他类方法调用match函数(例如,我可能希望在初始化文件Sample之前,能够检查文件是否具有相对于RE的有效名称)。你知道吗

简单地说,我想要这样的东西:

class Sample:
    RE = r'(?P<id>\d+)'
    STRICT_MATCHING = False
    re_ = re.compile(RE)  #
    match = re_.fullmatch if STRICT_MATCHING else re_.match  #

    def __init__(self, f):
        self.file = f
        self.basename = os.path.basename(os.path.splitext(self.file)[0])

        self.__dict__.update(self.match(self.basename).groupdict())

    @classmethod
    def valid(cls, f):
        basename, ext = os.path.splitext(os.path.basename(f))
        return cls.match(basename) and ext.lower() in ('.jpg', '.jpeg', '.png')


class DetailedSample(Sample):
    RE = r'(?P<id>\d+)_(?P<dir>[lr])_(?P<n>\d+)'
    STRICT_MATCHING = True

然而,这显然在子类中不起作用,因为在重新定义子类中的RESTRICT_MATCHING之后,用#标记的两行不会执行。你知道吗

是否有一种方法可以:

  • 保持第一种方法的功能(即基于regex的初始化)
  • 每个子类只编译regex并定义一次match方法
  • 允许从类方法调用match方法
  • 只需要在子类中重新定义regex字符串和STRICT_MATCHING参数?你知道吗

Tags: samplepath方法selfreidosmatch
3条回答

您可以缓存/记忆wiki.python.org中提到的已编译正则表达式,如果实例属性:

import os
import re
import functools

def memoize(obj):
    cache = obj.cache = {}

    @functools.wraps(obj)
    def memoizer(*args, **kwargs):
        if args not in cache:
            cache[args] = obj(*args, **kwargs)
        return cache[args]
    return memoizer


@memoize
def myRegExpCompiler(*args):
    print("compiling")
    return re.compile(*args)


class Sample:
    RE = r'(?P<id>\d+)'
    STRICT_MATCHING = False

    def __init__(self, f):
        self.file = f
        self.basename = os.path.basename(os.path.splitext(self.file)[0])

        re_ = myRegExpCompiler(self.__class__.RE) # use cls method!
        match = re_.fullmatch if self.__class__.STRICT_MATCHING else re_.match # use cls method!
        self.__dict__.update(match(self.basename).groupdict())


class DetailedSample(Sample):
    RE = r'(?P<id>\d+)_(?P<dir>[lr])_(?P<n>\d+)'
    STRICT_MATCHING = True


s1 = Sample("/asdf/2.jpg")
print(s1.id)
s2 = DetailedSample("/asdfadsf/2_l_2.jpg")
print(s2.id, s2.dir, s2.n)
s3 = DetailedSample("/asdfadsf/2_l_2.jpg")
print(s3.id, s3.dir, s3.n)

输出:

compiling
2
compiling
2 l 2
2 l 2

。。。正如你所看到的。表达式只编译了两次。你知道吗

你可以通过装饰类来做到这一点。你知道吗

这个装饰器检查STRICT_MATCHING属性并相应地设置match属性。你知道吗

def set_match(cls):
    match = cls.RE.fullmatch if cls.STRICT_MATCHING else cls.RE.match
    setattr(cls, 'match', match)
    return cls


@set_match
class Sample:
    RE = re.compile(r'(?P<id>\d+)')
    STRICT_MATCHING = False

    def __init__(self, f):
        self.file = f
        self.basename = os.path.basename(os.path.splitext(self.file)[0])
        self.__dict__.update(self.match(self.basename).groupdict())


@set_match
class DetailedSample(Sample):
    RE = re.compile(r'(?P<id>\d+)_(?P<dir>[lr])_(?P<n>\d+)')
    STRICT_MATCHING = True

使用元类可以获得相同的效果:

class MetaMatchSetter(type):

    def __new__(cls, clsname, bases, clsdict):
        rgx = clsdict['RE']
        match = rgx.fullmatch if clsdict['STRICT_MATCHING'] else rgx.match
        clsdict['match'] = match
        return super().__new__(cls, clsname, bases, clsdict)


class Sample(metaclass=MetaMatchSetter):
    ...

class DetailedSample(Sample):
    ...

但在我看来,使用类装饰器(或chepner的答案中描述的__init_subclass__)更具可读性和可理解性。你知道吗

您可以使用__init_subclass__来确保每个子类执行适当的工作。这将在公共基类继承的私有基类中定义。你知道吗

import os
import re


class _BaseSample:
    RE = r'(?P<id>\d+)'
    STRICT_MATCHING = False

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls._re = re.compile(cls.RE)
        cls.match = cls._re.fullmatch if cls.STRICT_MATCHING else cls._re.match


class Sample(_BaseSample):
    def __init__(self, f):
        self.file = f
        self.basename = os.path.basename(os.path.splitext(self.file)[0]
        self.__dict__.update(self.match(self.basename).groupdict())


class DetailedSample(Sample):
    RE = r'(?P<id>\d+)_(?P<dir>[lr])_(?P<n>\d+)'
    STRICT_MATCHING = True


s1 = Sample("/asdf/2.jpg")
print(s1.id)
s2 = DetailedSample("/asdfadsf/2_l_2.jpg")
print(s2.id, s2.dir, s2.n)

除非以后需要直接访问已编译的正则表达式,_re可以是_BaseSample.__init_subclass__的局部变量,而不是每个类的类属性。你知道吗

请注意,__init_subclass__还可以接受其他关键字参数,这些参数作为关键字参数提供给class语句本身。我不认为这样做有什么特别的好处;这只是您想要为设置RESTRICT_MATCHING提供什么接口的问题。详见Customizing Class Creation。你知道吗

相关问题 更多 >