Выберите функцию Python для вызова на основе регулярного выражения - PullRequest
54 голосов
/ 09 июля 2011

Можно ли поместить функцию в структуру данных без предварительного присвоения имени с 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?

Тело лямбда-функции строго ограничено, поэтому я не могу их использовать.

Изменить: Для справки, это больше похоже на реальный код, где я столкнулся с проблемой.

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) )

Ответы [ 14 ]

39 голосов
/ 09 июля 2011

Это основано на хороший ответ Уди .

Я думаю, что сложность создания анонимных функций - это нечто вроде красной селедки. Что вы действительно хотите сделать, так это сохранить связанный код и сделать его аккуратным. Поэтому я думаю, что декораторы могут работать на вас.

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
16 голосов
/ 09 июля 2011

Хороший СУХОЙ способ решения вашей актуальной проблемы:

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,
    ]]
14 голосов
/ 09 июля 2011

Продолжение Чистый подход Гарета с модульным автономным решением:

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)
13 голосов
/ 09 июля 2011

Если вы хотите сохранить чистое пространство имен, используйте del:

def myprint(msg):
    print msg
f_list = [ myprint ]
del myprint
f_list[0]('hi')
9 голосов
/ 09 июля 2011

Как вы сказали, это невозможно сделать.Но вы можете приблизиться к нему.

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

x = create_printer()

myprint здесь фактически анонимно, поскольку область видимости переменной, в которой он был создан, больше не доступна для вызывающей стороны.(См. замыкания в Python .)

6 голосов
/ 09 июля 2011

Если вас беспокоит загрязнение пространства имен, создайте свои функции внутри другой функции.Тогда вы «загрязняете» только локальное пространство имен функции create_functions, а не внешнее пространство имен.

def create_functions():
    def myprint(msg):
        print msg
    return [myprint]

f_list = create_functions()
f_list[0]('hi')
4 голосов
/ 09 июля 2011

Вы не должны делать это, потому что eval - зло, но вы можете скомпилировать код функции во время выполнения, используя FunctionType и compile:

>>> 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.
3 голосов
/ 09 июля 2011

Python действительно, действительно не хочет этого делать.Мало того, что она не имеет никакого способа определить многострочную анонимную функцию, но и определения функций не возвращают функцию, поэтому, даже если это было синтаксически допустимым ...

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

... itвсе равно не сработает.(Хотя я думаю, что если бы это было синтаксически верно, они бы заставили определения функций возвращать функцию, поэтому будет работать.)

Так что вы можете написать свою собственную функцию, чтобы сделатьстроку (используя, конечно, 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"""))
3 голосов
/ 09 июля 2011

Как и все сказанное, лямбда - единственный способ, но вы должны думать не об ограничениях лямбды, а о том, как их избежать - например, вы можете использовать списки, подсказки, понимания и т. Д., Чтобы делать то, что вы хотите:

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]

РЕДАКТИРОВАНИЕ с печатью (попробуйте взглянуть на модуль печати ) и поток управления:

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)
2 голосов
/ 09 июля 2011

Вы можете использовать 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')
...