web-dev-qa-db-fra.com

Choisissez Python fonction à appeler basée sur une expression régulière

Est-il possible de mettre une fonction dans une structure de données, sans lui donner au préalable un nom avec 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?

Le corps d'une fonction lambda est sévèrement limité, donc je ne peux pas les utiliser.

Edit: Pour référence, cela ressemble plus au code réel où j'ai rencontré le problème.

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
Tim

Ceci est basé sur Nice réponse d'Udi .

Je pense que la difficulté de créer des fonctions anonymes est un peu un hareng rouge. Ce que vous voulez vraiment faire, c'est de garder le code associé ensemble et de le rendre propre. Je pense donc que les décorateurs peuvent travailler pour vous.

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
Gareth Rees

Plus agréable DRY façon de résoudre votre problème réel:

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
Udi

Poursuite Gareth's approche propre avec une solution modulaire autonome:

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
Udi

Si vous souhaitez conserver un espace de noms propre, utilisez del:

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

Comme vous l'avez dit, cela ne peut pas être fait. Mais vous pouvez l'approcher.

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

x = create_printer()

myprint est effectivement anonyme ici, car la portée variable dans laquelle il a été créé n'est plus accessible à l'appelant. (Voir fermetures en Python .)

9
robert

Si vous êtes préoccupé par la pollution de l'espace de noms, créez vos fonctions à l'intérieur d'une autre fonction. Ensuite, vous ne "polluez" que l'espace de noms local du create_functions fonction et non l'espace de noms externe.

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

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

Vous ne devriez pas le faire car eval est mauvais, mais vous pouvez compiler du code de fonction au moment de l'exécution en utilisant FunctionType et 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
Udi

Comme tout le monde l'a dit, lambda est le seul moyen, mais vous ne devez pas penser aux limitations lambda, mais comment les éviter - par exemple, vous pouvez utiliser des listes, des dict, des compréhensions et ainsi de suite afin de faire ce que vous voulez:

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]

EDITÉ avec print (essayez de regarder le module pprint ) et le flux de contrôle:

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)
3
Artsiom Rudzenka

Python ne veut vraiment pas vraiment faire ça. Non seulement il n'a aucun moyen de définir une fonction anonyme multiligne, mais les définitions de fonction ne renvoient pas la fonction, donc même si celle-ci était syntaxiquement valide ...

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

... ça ne marcherait toujours pas. (Bien que je suppose que si elle était syntaxiquement valide, ils feraient en sorte que les définitions de fonction retournent la fonction, donc cela serait fonctionner.)

Vous pouvez donc écrire votre propre fonction pour créer une fonction à partir d'une chaîne (en utilisant exec bien sûr) et passer une chaîne entre guillemets triples. C'est un peu laid syntaxiquement, mais ça marche:

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.__= "<anonymous>"

    cache[text] = func
    return func

    # never executed; forces func to be local (a tiny bit more speed)
    func = None

Usage:

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

La seule façon de créer une fonction anonyme est avec lambda, et comme vous le savez, ils ne peuvent contenir qu'une seule expression.

Vous pouvez créer un certain nombre de fonctions avec le même nom afin qu'au moins vous n'ayez pas à penser à de nouveaux noms pour chacune.

Ce serait formidable d'avoir des fonctions vraiment anonymes, mais la syntaxe de Python ne peut pas les supporter facilement.

2
Ned Batchelder

Vous pouvez utiliser 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')
2
codeape

Personnellement, je voudrais juste le nommer quelque chose l'utiliser et ne pas s'inquiéter à ce sujet "traîner". La seule chose que vous gagnerez en utilisant des suggestions telles que la redéfinir plus tard ou en utilisant del pour supprimer le nom de l'espace de noms est un potentiel de confusion ou de bugs si quelqu'un vient plus tard et déplace du code sans groking ce que vous faites.

2
John Gaines Jr.

La seule option est d'utiliser une expression lambda, comme vous le mentionnez. Sans cela, ce n'est pas possible. C'est ainsi que fonctionne python.

1
thunderflower