Frage Wählen Sie die Python-Funktion zum Aufrufen basierend auf einer Regex


Ist es möglich, eine Funktion in eine Datenstruktur zu stellen, ohne sie vorher zu benennen? 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?

Der Körper einer Lambda-Funktion ist stark eingeschränkt, so dass ich sie nicht verwenden kann.

Edit: Als Referenz ist dies eher der Code in der realen Welt, in dem ich auf das Problem gestoßen bin.

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

54
2017-07-08 20:05


Ursprung


Antworten:


Dies basiert auf Udi's nette Antwort.

Ich denke, dass die Schwierigkeit, anonyme Funktionen zu erstellen, ein bisschen Ablenkungsmanöver ist. Was Sie wirklich tun möchten, ist, verwandten Code zusammenzuhalten und den Code ordentlich zu machen. Also ich denke, Dekorateure können für Sie arbeiten.

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

39
2017-07-08 22:11



Nicer DRY Weg, um Ihr tatsächliches Problem zu lösen:

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,
    ]]

16
2017-07-08 21:28



Auch weiterhin Gareth sauberer Ansatz mit einer modularen, eigenständigen Lösung:

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)

14
2017-07-09 13:30



Wenn Sie einen sauberen Namespace behalten möchten, verwenden Sie del:

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

13
2017-07-08 20:16



Wie Sie gesagt haben, kann dies nicht getan werden. Aber Sie können es annähern.

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

x = create_printer()

myprint ist hier effektiv anonym, da der Variablenbereich, in dem er erstellt wurde, für den Aufrufer nicht mehr zugänglich ist. (Sehen Schließungen in Python.)


9
2017-07-08 20:24



Wenn Sie Bedenken haben, den Namensraum zu verschmutzen, erstellen Sie Ihre Funktionen innerhalb einer anderen Funktion. Dann "verschmutzen" Sie nur den lokalen Namespace der create_functions Funktion und nicht der äußere Namespace.

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

f_list = create_functions()
f_list[0]('hi')

6
2017-07-08 20:24



Sie sollten es nicht tun, weil eval schlecht ist, aber Sie können den Funktionscode während der Laufzeit mithilfe von kompilieren FunctionType und 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.

4
2017-07-08 20:32