【问题标题】:Python - Overriding the initialisation of a class variablePython - 覆盖类变量的初始化
【发布时间】:2020-03-18 14:51:15
【问题描述】:

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

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 函数(例如,我可能希望能够在初始化@987654326 之前检查文件是否具有有效的名称 - 相对于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 后,标有# 的两行将不会执行。

有没有一种方法可以:

  • 保留第一种方法的功能(即基于正则表达式的初始化);
  • 每个子类只编译一次正则表达式并定义一次匹配方法;
  • 允许从类方法中调用匹配方法;
  • 只需要在子类中重新定义正则表达式字符串和STRICT_MATCHING参数?

【问题讨论】:

  • 你总是可以使用元类来做到这一点,但我不确定这是一个解决方案还是另一个问题...... ;-)
  • 您的方法将失败,因为RE = ...match = 的行仅在模块加载时评估一次。因此更改RE = ...STRICT_MATCHING = ... 没有没有效果
  • 您可以简单地自己提供RE = re.compile(r'...'),而不是让班级为您调用re.compile
  • @thebjorn __init_subclass__ 的引入正是为了避免这里需要一个成熟的元类 :)
  • re 模块会缓存最近编译的模式,因此一遍又一遍地重复编译相同的几个模式可能不会受到惩罚(如果它们是仍在缓存中)。

标签: python python-3.x inheritance class-variables


【解决方案1】:

您可以缓存/记忆已编译的正则表达式,如 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

...正如您所看到的那样。表达式只编译两次。

【讨论】:

    【解决方案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):
        ...
    

    但在我看来,使用类装饰器(或 __init_subclass__,如 chepner 的回答中所述)更具可读性和可理解性。

    【讨论】:

    • 不仅可读性更高,而且由于元类通常不是可组合的,因此引入元类会使未来的继承更加困难。尝试同时继承 Sample 和使用不同元类的类的人可能会遇到冲突。
    【解决方案3】:

    您可以使用__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

    【讨论】:

    • 我总是忘记__init_subclass__ - 你是对的,这可能是最好的方法。
    • 我最喜欢这个解决方案,它对我来说似乎是最pythonic的,因为它只是覆盖了一个为此目的而创建的方法。需要注意的是,__init_subclass__ 仅在 python>=3.6 中可用,因此在旧版本中,您必须使用其中一种类装饰器方法。
    猜你喜欢
    • 2012-01-20
    • 2012-10-22
    • 1970-01-01
    • 2015-08-24
    • 2017-07-18
    • 2018-10-25
    • 2016-11-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多