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时,都会重新编译正则表达式。
  • match函数不能从Sample中的其他类方法调用(例如,我可能希望能够在初始化文件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后,标有#的两行不会执行。

有没有一种方法可以:

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

您可以使用__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的问题。有关详细信息,请参阅自定义类创建。

您可以通过装饰类来做到这一点。

此修饰器检查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__(更具可读性和可理解性。

您可以缓存/记忆 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

。如您所见,Reg. 表达式只编译了两次。

最新更新