【问题标题】:Choose Python function to call based on a regex根据正则表达式选择要调用的 Python 函数
【发布时间】:2011-07-08 20:05:00
【问题描述】:

是否可以将函数放入数据结构中,而无需先用def 为其命名?

# This is the behaviour I want. Prints "hi".
def myprint(msg):
    print msg
f_list = [ myprint ]
f_list[0]('hi')
# The word "myprint" is never used again. Why litter the namespace with it?

lambda 函数的主体受到严格限制,所以我不能使用它们。

编辑:作为参考,这更像是我遇到问题的真实代码。

def handle_message( msg ):
    print msg
def handle_warning( msg ):
    global num_warnings, num_fatals
    num_warnings += 1
    if ( is_fatal( msg ) ):
        num_fatals += 1
handlers = (
    ( re.compile( '^<\w+> (.*)' ), handle_message ),
    ( re.compile( '^\*{3} (.*)' ), handle_warning ),
)
# There are really 10 or so handlers, of similar length.
# The regexps are uncomfortably separated from the handler bodies,
# and the code is unnecessarily long.

for line in open( "log" ):
    for ( regex, handler ) in handlers:
        m = regex.search( line )
        if ( m ): handler( m.group(1) )

【问题讨论】:

  • 不,不是。 # The word "myprint" is never used again. Why litter the namespace with it?你为什么要花这么多时间来摆脱一条对你没有任何伤害的线路?
  • @phant0m, @Udi:我希望我的代码漂亮且易于阅读。在现实生活中,我有一个正则表达式-函数对对的列表,并在与正则表达式匹配的字符串上运行函数/处理程序。处理程序足够小,可以使列表之外的定义变得丑陋和不恰当。
  • 我现在已经添加了真正的问题。我通常不喜欢这样做,因为它使问题更加具体。我可能会从发布它中学到更多,但不是通过标题找到问题的未来访问者。 (ping @phant0m)
  • 那些函数名是很好的文档。如果你要让它们匿名,你的代码的阅读者将不得不花费更多的大脑周期来理解这些函数的作用。
  • 如果你真的想要这些东西,你可能想切换到 perl。我知道 perl,但为了清楚起见,我使用 python。你可以正确地建模这个 Pattern,或者破解它。在后一种情况下,我认为命名空间污染不是您的主要问题。

标签: python anonymous-function lambda


【解决方案1】:

这是基于Udi's nice answer

我认为创建匿名函数的难度有点像红鲱鱼。您真正想做的是将相关代码保持在一起,并使代码整洁。所以我认为装饰器可能适合你。

import re

# List of pairs (regexp, handler)
handlers = []

def handler_for(regexp):
    """Declare a function as handler for a regular expression."""
    def gethandler(f):
        handlers.append((re.compile(regexp), f))
        return f
    return gethandler

@handler_for(r'^<\w+> (.*)')
def handle_message(msg):
    print msg

@handler_for(r'^\*{3} (.*)')
def handle_warning(msg):
    global num_warnings, num_fatals
    num_warnings += 1
    if is_fatal(msg):
        num_fatals += 1

【讨论】:

【解决方案2】:

更好的 DRY 方法来解决您的实际问题:

def message(msg):
    print msg
message.re = '^<\w+> (.*)'

def warning(msg):
    global num_warnings, num_fatals
    num_warnings += 1
    if ( is_fatal( msg ) ):
        num_fatals += 1
warning.re = '^\*{3} (.*)'

handlers = [(re.compile(x.re), x) for x in [
        message,
        warning,
        foo,
        bar,
        baz,
    ]]

【讨论】:

  • 比我的尝试好多了。在提出更多想法之前,我真的应该阅读可用的数据结构。谢谢!
【解决方案3】:

继续使用Gareth's 清洁方法和模块化独立解决方案:

import re

# in util.py
class GenericLogProcessor(object):

    def __init__(self):
      self.handlers = [] # List of pairs (regexp, handler)

    def register(self, regexp):
        """Declare a function as handler for a regular expression."""
        def gethandler(f):
            self.handlers.append((re.compile(regexp), f))
            return f
        return gethandler

    def process(self, file):
        """Process a file line by line and execute all handlers by registered regular expressions"""
        for line in file:
            for regex, handler in self.handlers:
                m = regex.search(line)
                if (m):
                  handler(m.group(1))      

# in log_processor.py
log_processor = GenericLogProcessor()

@log_processor.register(r'^<\w+> (.*)')
def handle_message(msg):
    print msg

@log_processor.register(r'^\*{3} (.*)')
def handle_warning(msg):
    global num_warnings, num_fatals
    num_warnings += 1
    if is_fatal(msg):
        num_fatals += 1

# in your code
with open("1.log") as f:
  log_processor.process(f)

【讨论】:

  • 我不得不说,这个不错,很紧凑。东西汇集在一个地方。
  • 完美使用装饰器并保持紧凑!
【解决方案4】:

如果你想保持一个干净的命名空间,使用 del:

def myprint(msg):
    print msg
f_list = [ myprint ]
del myprint
f_list[0]('hi')

【讨论】:

    【解决方案5】:

    正如你所说,这是不可能的。但是你可以近似它。

    def create_printer():
      def myprint(x):
        print x
      return myprint
    
    x = create_printer()
    

    myprint 在这里实际上是匿名的,因为调用者不再可以访问创建它的变量范围。 (见closures in Python。)

    【讨论】:

    • 我不知道我对最后一行的感觉如何。作用域在闭包inside中持续存在的python中闭包的全部意义不是吗?话虽如此,我非常喜欢这个解决方案。
    【解决方案6】:

    如果您担心会污染命名空间,请在另一个函数中创建您的函数。那么你只是在“污染”create_functions 函数的本地命名空间,而不是外部命名空间。

    def create_functions():
        def myprint(msg):
            print msg
        return [myprint]
    
    f_list = create_functions()
    f_list[0]('hi')
    

    【讨论】:

    • 除了命名空间污染之外,我还为 1. 使用临时名称和 2. 当它只使用一次时必须在不同的地方定义它而烦恼。尽管这解决了命名空间问题,但它只会加剧其他问题。
    【解决方案7】:

    你不应该这样做,因为 eval 是邪恶的,但你可以在运行时使用 FunctionTypecompile 编译函数代码:

    >>> def f(msg): print msg
    >>> type(f)
     <type 'function'>
    >>> help(type(f))
    ...
    class function(object)
     |  function(code, globals[, name[, argdefs[, closure]]])
     |
     |  Create a function object from a code object and a dictionary.
     |  The optional name string overrides the name from the code object.
     |  The optional argdefs tuple specifies the default argument values.
     |  The optional closure tuple supplies the bindings for free variables.    
    ...
    
    >>> help(compile)
    Help on built-in function compile in module __builtin__:
    
    compile(...)
        compile(source, filename, mode[, flags[, dont_inherit]]) -> code object
    
        Compile the source string (a Python module, statement or expression)
        into a code object that can be executed by the exec statement or eval().
        The filename will be used for run-time error messages.
        The mode must be 'exec' to compile a module, 'single' to compile a
        single (interactive) statement, or 'eval' to compile an expression.
        The flags argument, if present, controls which future statements influence
        the compilation of the code.
        The dont_inherit argument, if non-zero, stops the compilation inheriting
        the effects of any future statements in effect in the code calling
        compile; if absent or zero these statements do influence the compilation,
        in addition to any features explicitly specified.
    

    【讨论】:

    • 好主意。出于兴趣,evalcompile 之间的道德区别是什么?
    • 绝妙的答案。 这种新颖的方法不亚于一种强大的机制,用于在运行时动态定义 Python 可调用对象(例如函数、方法),并提供闭包支持!我一直在寻找像这样的东西完全的时间比我愿意承认的要长。感谢您将拼图拼凑在一起,Udi
    【解决方案8】:

    创建匿名函数的唯一方法是使用lambda,如您所知,它们只能包含一个表达式。

    您可以创建多个具有相同名称的函数,这样至少您不必为每个函数考虑新名称。

    拥有真正的匿名函数会很棒,但 Python 的语法不能轻易支持它们。

    【讨论】:

    • "但是 Python 的语法不能轻易支持它们。"你能详细说明一下吗? (或资源链接)谢谢。
    • @phant0m: 用于块定界的缩进模型不支持表达式内的开始/结束块(例如在括号内),并且似乎这不能轻易添加(实现起来非常复杂或使 all 表达式中的缩进有意义,这会破坏多行表达式)。因此,您不能在 lambda 表达式中允许多个语句,因为您不知道在哪里结束(没有 DEDENT 令牌)。我敢打赌,邮件列表上还有更多材料。
    • 啊,是的,这很有道理!我并没有真正想太多去思考它们将在何处以及如何实际使用以及这意味着什么:) 谢谢!
    • 我找不到很好的描述,但是已经重新散列了很多次,@delnan 是对的:基于缩进的语法无法容纳表达式中的语句。
    • 相反,Scheme 派生的 R 允许这样做: function(x,y,z) {w
    【解决方案9】:

    正如大家所说的 lambda 是唯一的方法,但你必须考虑的不是 lambda 限制,而是如何避免它们 - 例如,你可以使用列表、字典、理解等来做你想做的事:

    funcs = [lambda x,y: x+y, lambda x,y: x-y, lambda x,y: x*y, lambda x: x]
    funcs[0](1,2)
    >>> 3
    funcs[1](funcs[0](1,2),funcs[0](2,2))
    >>> -1
    [func(x,y) for x,y in zip(xrange(10),xrange(10,20)) for func in funcs]
    

    使用打印编辑(尝试查看pprint module)和控制流:

    add = True
    (funcs[0] if add else funcs[1])(1,2)
    >>> 3
    
    from pprint import pprint
    printMsg = lambda isWarning, msg: pprint('WARNING: ' + msg) if isWarning else pprint('MSG:' + msg)
    

    【讨论】:

    • 这似乎相当复杂,并且主要适用于没有流量控制的数学表达式。甚至可以这样写打印机吗?
    • 我认为这里不仅可以用于数学,而且流控制也可以在这里使用-请参阅我的更新
    • 我明白了,这对于一些小的解决方法可能会派上用场。谢谢。
    • 不客气,如果我的解决方案不能满足您的需求,我们深表歉意。我只是一个初学者,正在尝试使用 python。
    【解决方案10】:

    Python 真的,真的不想这样做。不仅没有办法定义多行匿名函数,而且函数定义也不返回函数,所以即使这在语法上是有效的......

    mylist.sort(key=def _(v):
                        try:
                            return -v
                        except:
                            return None)
    

    ... 还是不行。 (虽然我猜如果它在语法上是有效的,他们会让函数定义返回函数,所以它工作。)

    因此,您可以编写自己的函数来从字符串创建函数(当然使用exec)并传入一个三引号字符串。这在语法上有点难看,但它确实有效:

    def function(text, cache={}):
    
        # strip everything before the first paren in case it's "def foo(...):"
        if not text.startswith("("):
            text = text[text.index("("):]
    
        # keep a cache so we don't recompile the same func twice
        if text in cache:
            return cache[text]
    
        exec "def func" + text
        func.__name__ = "<anonymous>"
    
        cache[text] = func
        return func
    
        # never executed; forces func to be local (a tiny bit more speed)
        func = None
    

    用法:

    mylist.sort(key=function("""(v):
                                    try:
                                        return -v
                                    except:
                                        return None"""))
    

    【讨论】:

    • 除了语法高亮,我觉得三引号的自定义函数一点都不难看。我不知道 func = None 技巧——这在哪里记录?
    • 如果你在函数中赋值给一个变量,这个变量是局部的。它是在编译时确定的,而不是在执行时确定的,因此它可能超出了实际的执行路径。请参阅:docs.python.org/reference/executionmodel.html,特别是“如果名称绑定在块中,则它是该块的局部变量。”
    • 局部变量的速度优势:wiki.python.org/moin/PythonSpeed/… 我想如果我要做一些如此骇人听闻的事情,我至少会尽可能快。 :-)
    【解决方案11】:

    就我个人而言,我只是将它命名为使用它的东西,而不是担心它“闲逛”。通过使用建议(例如稍后重新定义它或使用del 将名称从命名空间中删除),您将获得的唯一好处是,如果有人稍后出现并移动一些代码而不了解您的内容,则可能会造成混淆或错误正在做。

    【讨论】:

    • 除了命名空间垃圾之外,我还为 1. 使用临时名称和 2. 只使用一次时必须在不同的地方定义它而烦恼。
    【解决方案12】:

    你可以使用exec:

    def define(arglist, body):
        g = {}
        exec("def anonfunc({0}):\n{1}".format(arglist,
                                         "\n".join("    {0}".format(line)
                                                   for line in body.splitlines())), g)
        return g["anonfunc"]
    
    f_list = [define("msg", "print(msg)")]
    f_list[0]('hi')
    

    【讨论】:

    • 这也适用于多行代码和缩进。好的。唯一的exec 开销将是一次,在define 调用上,对吧?
    • 是的,开销只有一次。
    【解决方案13】:

    唯一的选择是使用 lambda 表达式,就像你提到的那样。没有它,这是不可能的。这就是python的工作方式。

    【讨论】:

    • 在这种情况下,或者我需要两个语句的情况下,lambda 函数不起作用,对吧?
    • 没错。由于 print 不是一个声明,它不会起作用。而且你只能在 lambda 中有一个语句。但是,您可以使用 and 运算符做出相当长的语句。但我想这不是你真正想要的。
    • @thunderflower:lambda 中不能有任何语句,只能有一个表达式。
    • 是的。对于那个很抱歉。表达就是我的意思。
    【解决方案14】:

    如果您的函数足够复杂以至于无法放入 lambda 函数中,那么,为了便于阅读,最好还是在普通块中定义它。

    【讨论】:

    • 你认为“print x”很复杂吗?
    • @Tim Nordenfur:他特别说“lambda 函数的主体受到严重限制,所以我不能使用它们。”我假设(可能是错误的)print 语句就是一个例子。
    • 虽然肯定有很多太长的匿名函数,但我确信有一些示例可以使用类似 lambda 的语法,即使它太“复杂”。 print x 是一个反例,任何不是表达式的语句也是如此。
    • print 可以通过from __future__ import print_statement 用作函数。 if 语句可以由lambda 处理。如果他需要使用try...except 之类的东西,那么为它定义一个块是个好主意。大多数其他语句要么内置于lambda,要么对 lambda 函数无用。也许exec 可以作为反例?
    猜你喜欢
    • 1970-01-01
    • 2015-07-23
    • 1970-01-01
    • 2017-12-10
    • 1970-01-01
    • 2017-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多