Python - 覆盖 class 变量的初始化
Python - Overriding the initialisation of a class variable
我有一个 superclass 和一个 subclass 需要根据正则表达式以不同方式处理它们的初始化。请参阅下面的工作示例。
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
中的其他 class 方法调用 match
函数(例如,我可能希望能够检查文件是否具有有效名称 - 相对到 RE
- 在从中初始化 Sample
之前)。
简单地说,我想要这样的东西:
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
然而,这在子class中显然是行不通的,因为标有#
的两行在重定义RE
和STRICT_MATCHING
在子class.
有没有一种方法可以:
- 保留第一种方法的功能(即基于正则表达式的初始化);
- 只编译正则表达式并为每个子定义一次匹配方法class;
- 允许从 class 方法调用匹配方法;
- 只需要在subclasses?
中重新定义regex字符串和STRICT_MATCHING
参数
您可以 cache/memoize wiki.python.org 中提到的已编译正则表达式,如果实例属性:
,则需要使用 class 属性
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
...如您所见。表达式只编译两次。
您可以通过装饰 classes 来做到这一点。
此装饰器检查 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
使用 metaclass:
可以获得同样的效果
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):
...
但在我看来,使用 class 装饰器(或 chepner 的回答中描述的 __init_subclass__
)更具可读性和可理解性。
您可以使用 __init_subclass__
来确保每个子 class 完成适当的工作。这将在您的 public 基 class 继承自的私有基 class 中定义。
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__
的局部变量,而不是每个 class 的 class 属性。
请注意,__init_subclass__
还可以采用其他关键字参数,作为关键字参数提供给 class
语句本身。我不认为这样做有什么特别的好处;这只是你想为设置 RE
和 STRICT_MATCHING
提供什么接口的问题。有关详细信息,请参阅 Customizing Class Creation。
我有一个 superclass 和一个 subclass 需要根据正则表达式以不同方式处理它们的初始化。请参阅下面的工作示例。
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
中的其他 class 方法调用match
函数(例如,我可能希望能够检查文件是否具有有效名称 - 相对到RE
- 在从中初始化Sample
之前)。
简单地说,我想要这样的东西:
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
然而,这在子class中显然是行不通的,因为标有#
的两行在重定义RE
和STRICT_MATCHING
在子class.
有没有一种方法可以:
- 保留第一种方法的功能(即基于正则表达式的初始化);
- 只编译正则表达式并为每个子定义一次匹配方法class;
- 允许从 class 方法调用匹配方法;
- 只需要在subclasses? 中重新定义regex字符串和
STRICT_MATCHING
参数
您可以 cache/memoize wiki.python.org 中提到的已编译正则表达式,如果实例属性:
,则需要使用 class 属性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
...如您所见。表达式只编译两次。
您可以通过装饰 classes 来做到这一点。
此装饰器检查 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
使用 metaclass:
可以获得同样的效果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):
...
但在我看来,使用 class 装饰器(或 chepner 的回答中描述的 __init_subclass__
)更具可读性和可理解性。
您可以使用 __init_subclass__
来确保每个子 class 完成适当的工作。这将在您的 public 基 class 继承自的私有基 class 中定义。
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__
的局部变量,而不是每个 class 的 class 属性。
请注意,__init_subclass__
还可以采用其他关键字参数,作为关键字参数提供给 class
语句本身。我不认为这样做有什么特别的好处;这只是你想为设置 RE
和 STRICT_MATCHING
提供什么接口的问题。有关详细信息,请参阅 Customizing Class Creation。