在 Python 中创建动态部分可调用对象

问题描述

我在 YAML 配置文件中定义了一个正则表达式。

为了方便起见,我将在这里使用字典:

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_repattern 调用的原始函数类似,但没有 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

虽然,我发现上述实现有两个可维护性问题:

  1. 我正在使用该 >>> r2 = rule_parser_re_2(**rule_2) >>> r2(string=content[2]) <re.Match object; span=(0,match='second'> 语句,该语句迫使我为我想要支持的每个 if 方法修改函数
  2. 我需要明确指定参数,而不仅仅是解压“**kwargs

我的目标/怀疑:

  • 有什么方法可以使上述功能更加动态和可维护吗?
  • refunctools.partial() 是适合这项工作的工具吗?
  • 如果可以,它们可以组合在一起吗?

谢谢!

解决方法

与其尝试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.matchetc.)的签名不匹配,您需要编写自己的函数。它可以使用带有命名参数的包装函数来适应接口(尽管这涉及为您发明的 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 在这里不是合适的工具。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...