问题描述
为了方便起见,我将在这里使用字典:
white-space: pre;
我希望能够在函数中解析该规则。
如果我们假设这些值不会改变,那么我可以做这样的事情。
导入模块:
rule_1 = {
'kind': 'regex','method': 'match','args': None,'kwargs': {
'pattern': "[a-z_]+",'flags': re.X,'string': 's_test.log',}
}
import re
from operator import methodcaller
from functools import partial
它按预期工作:
def rule_parser_re_1(*,kind,method,args=None,kwargs=None):
if args is None: args = []
if kwargs is None: kwargs = {}
mc = methodcaller(method,**kwargs)
return mc(re)
现在,假设在定义配置字典时我没有要解析的字符串。
例如假设它是文件中的特定行,只能在运行时访问。
>>> rule_parser_re_1(**rule_1)
<re.Match object; span=(0,6),match='s_test'>
我的第二条规则,其中“line_number”(即 myfile = """
first line
second line
third line
"""
io_myfile = io.StringIO(myfile)
content = io_myfile.readlines()
)替换“string”(即 int
)。
str
我的理解是,我应该能够通过定义部分 rule_2 = {
'kind': 'regex','line_number': 2,}
}
函数来解决这个问题。
此类函数的行为应与使用 rule_parser_re
和 pattern
调用的原始函数类似,但没有 flags
。
我想出了以下功能:
string
这似乎也能正常工作:
def rule_parser_re_2(*,kwargs=None):
if args is None: args = []
if kwargs is None: kwargs = {}
if kind == 'regex' and method == 'match':
pa = partial(re.match,pattern=kwargs['pattern'],flags=kwargs['flags'])
return pa
虽然,我发现上述实现有两个可维护性问题:
- 我正在使用该
>>> r2 = rule_parser_re_2(**rule_2) >>> r2(string=content[2]) <re.Match object; span=(0,match='second'>
语句,该语句迫使我为我想要支持的每个if
方法修改函数; - 我需要明确指定参数,而不仅仅是解压“**kwargs”
我的目标/怀疑:
谢谢!
解决方法
与其尝试partial或methodcaller,为什么不直接调用函数,只使用kwargs,而是使用配置驱动大部分kwargs/args内容?我为此使用了一个闭包,其中准备好的“记住”配置。
请注意,我的最终调用并不关心 string 是 re.match 的关键字。我发现您的示例与正则表达式特定内容存在相当多的耦合,其中一些如 re.X
无法在没有进一步操作的情况下存储在 YAML 中。
同样,partial/methodcaller 调用函数的方式不应该关心值来自文件中的哪个行号,这太耦合了。如果必须,请在配置中添加其他内容,不在 kwargs
下,处理运行时参数获取。
所以我改变了一些东西。我相信,但你可能不同意,在调用解析规则时,调用函数不应该知道参数是如何被调用的。好吧,也就是说,除非您的规则只是风格上的正则表达式,在这种情况下,您不需要在配置中使用 kind
。
这是替代方法的快速、不完美的草图。详细信息将取决于您希望如何使用它。
我还对 *args
的处理进行了下注,但如果您必须这样做,它可能会以相同的方式执行。
import importlib
rule_1 = {
'kind': 're','method': 'match','args': None,"positional_mapper" : ["string"],'kwargs': {
'pattern': "[a-z_]+",# I don't know how this would be stored in a YAML
# 'flags': re.X,'string': 's_test.log',}
}
rule_2 = {
'kind': 're',}
}
def prep(config):
mod = app_urls = importlib.import_module(config["kind"])
f = getattr(mod,config["method"])
pre_args = config.get("args") or []
pre_kwargs = config.get("kwargs") or {}
positional_mapper = config["positional_mapper"]
def prepped(*args,**kwargs):
kwargs2 = pre_kwargs.copy()
for value,argname in zip(args,positional_mapper):
kwargs2[argname] = value
kwargs2.update(**kwargs)
return f(**kwargs2)
return prepped
parsed_rule1 = prep(rule_1)
print ("#1",parsed_rule1("second line"))
print ("#2",parsed_rule1())
parsed_rule2 = prep(rule_2)
print ("#3",parsed_rule2("second line"))
print ("#3.5",parsed_rule2(string="second line"))
print ("#4",parsed_rule2())
正如预期的那样,调用 #4 chokes,因为它缺少要放入 string
的参数。
#1 <re.Match object; span=(0,6),match='second'>
#2 <re.Match object; span=(0,match='s_test'>
#3 <re.Match object; span=(0,match='second'>
#3.5 <re.Match object; span=(0,match='second'>
Traceback (most recent call last):
File "test_299_dyn.py:57",in <module>
print ("#4",parsed_rule2())
File "test_299_dyn.py:44",in prepped
return f(**kwargs2)
TypeError: match() missing 1 required positional argument: 'string'
,
您不必创建偏函数。您可以先编译模式,然后调用所需的方法:
rule_2 = {
'kind': 'regex','flags': re.X,# 'line_number': 2,commented out this line
}
}
content = ['','first line','second line','third line']
pattern = re.compile(**rule_2['kwargs'])
method = getattr(pattern,rule_2['method'])
>>> method(content[2])
<re.Match object; span=(0,match='second'>
如果你想保留行号,你可以这样做:
rule_2 = {
'kind': 'regex','line_number': 2,}
}
content = ['','third line']
def rule_parser_re(*,kind,method,args=None,kwargs=None):
copied_kwargs = kwargs.copy()
line_number = copied_kwargs.pop('line_number')
pattern = re.compile(**copied_kwargs)
method = getattr(pattern,method)
return method,line_number
parser,line_number = rule_parser_re(**rule_2)
>>> parser(content[line_number])
<re.Match object; span=(0,match='second'>
,
由于您的第二个架构与 re.match
(etc.)的签名不匹配,您需要编写自己的函数。它可以使用带有命名参数的包装函数来适应接口(尽管这涉及为您发明的 line_number
参数确定一个位置,如果您关心 args
)。它还可以使用 getattr
,这相当于 operator.methodcaller
的某些普通用法:
def rule2(kind,args,kwargs):
return _rule2(getattr(re,method),*args or (),**kwargs or {})
def _rule2(f,pattern,line_number,flags):
return lambda content: f(pattern,content[line_number],flags)
注意 content
是剩下的参数,因为只有行号会使文件内容未知;由于它不是直接底层函数的参数,因此 partial
在这里不是合适的工具。