web-dev-qa-db-ja.com

正規表現に基づいて呼び出すPython関数を選択します

最初に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) )
54
Tim

これは、 diの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
39
Gareth Rees

より良い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,
    ]]
16
Udi

継続 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)
14
Udi

名前空間をきれいに保ちたい場合は、delを使用します。

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

あなたが述べたように、これはできません。ただし、近似することはできます。

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

x = create_printer()

myprintは、それが作成された変数スコープに呼び出し元がアクセスできないため、ここでは事実上匿名です。 ( Pythonのクロージャ を参照してください。)

9
robert

名前空間の汚染が心配な場合は、別の関数内に関数を作成してください。次に、create_functions関数のローカル名前空間のみを「汚染」し、外側の名前空間は汚染しません。

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

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

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.
4
Udi

ラムダが唯一の方法であると述べましたが、ラムダの制限についてではなく、それらを回避する方法を考える必要があります-たとえば、リスト、辞書、内包表記などを使用して、必要なことを行うことができます:

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]

Print( 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)
3

Pythonは本当にこれをしたくありません。複数行の匿名関数を定義する方法がないだけでなく、関数定義が関数を返さないため、構文的に有効であっても...

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

...それでも動作しません。 (構文的に有効であるかどうかは推測しますが、関数定義が関数を返すようにするので、wouldは機能します。)

したがって、文字列から関数を作成する独自の関数を作成し(もちろん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.__= "<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
kindall

無名関数を作成する唯一の方法はlambdaを使用することであり、ご存じのように、それらは単一の式のみを含むことができます。

同じ名前の関数をいくつでも作成できるため、少なくともそれぞれの関数の新しい名前を考える必要はありません。

本当に匿名の関数があれば素晴らしいのですが、Pythonの構文はそれらを簡単にサポートできません。

2
Ned Batchelder

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

個人的には、使用するものに名前を付け、「ぶらぶら」することを心配しません。後で再定義したり、delを使用して名前空間から名前を削除するなどの提案を使用することで得られる唯一のことは、後で誰かがやって来て、あなたがしていることを学んでいます。

2
John Gaines Jr.

あなたが言及したように、唯一のオプションはラムダ式を使用することです。それがなければ、それは不可能です。それがpythonの仕組みです。

1
thunderflower