6

Python の演算子記号またはキーワードの 1 つをstring として受け取り、オペランドとともに評価して結果を返す関数が必要です。このような:

>>> string_op('<=', 3, 3)
True
>>> string_op('|', 3, 5)
7
>>> string_op('and', 3, 5)
True
>>> string_op('+', 5, 7)
12
>>> string_op('-', -4)
4

文字列が安全であると想定することはできません。二項演算子をマッピングするだけで十分ですが、それらすべてを取得できればさらに嬉しいです。

私の現在の実装では、シンボルをoperator モジュールの関数に手動でマップします。

import operator

def string_op(op, *args, **kwargs):
    """http://docs.python.org/2/library/operator.html"""
    symbol_name_map = {
        '<': 'lt',
        '<=': 'le',
        '==': 'eq',
        '!=': 'ne',
        '>=': 'ge',
        '>': 'gt',
        'not': 'not_',
        'is': 'is_',
        'is not': 'is_not',
        '+': 'add', # conflict with concat
        '&': 'and_', # (bitwise)
        '/': 'div',
        '//': 'floordiv',
        '~': 'invert',
        '%': 'mod',
        '*': 'mul',
        '|': 'or_', # (bitwise)
        'pos': 'pos_',
        '**': 'pow',
        '-': 'sub', # conflicts with neg
        '^': 'xor',
        'in': 'contains',
        '+=': 'iadd', # conflict with iconcat
        '&=': 'iand',
        '/=': 'idiv',
        '//=': 'ifloordiv',
        '<<=': 'ilshift',
        '%=': 'imod',
        '*=': 'imul',
        '|=': 'ior',
        '**=': 'ipow',
        '>>=': 'irshift',
        '-=': 'isub',
        '^=': 'ixor',
    }
    if op in symbol_name_map:
        return getattr(operator, symbol_name_map[op])(*args, **kwargs)
    else:
        return getattr(operator, op)(*args, **kwargs)

このソリューションは、オーバーロードされた演算子 ( add/concatおよびsub/ ) で失敗しますneg。これらのケースを検出し、型を検出したり、引数をカウントして正しい関数名を選択したりするためのチェックを追加できますが、それは少し見苦しく感じます。ここで良いアイデアが得られない場合は、これを使用します。

私を悩ませているのは、Pythonがすでにこれを行っていることです。シンボルを演算子関数にマップする方法は既にわかっていますが、私が知る限り、その機能はプログラマーに公開されていません。ピクルス化プロトコルに至るまで、 Pythonの他のすべてがプログラマーに公開されているようです。それで、これはどこですか?またはなぜそうではないのですか?

4

4 に答える 4

6

Python はシンボルを関数にマップしません。特別なメソッドoperatorを呼び出してシンボルを解釈します。dunder

たとえば、 と書いても;2 * 3は呼び出されません。、、または同等の C タイプ (スロットとは両方とも と の両方と同等)mul(2, 3)を使用するかどうかを判断する C コードを呼び出します。として C 拡張モジュールから同じコードを呼び出すことができます。へのソースを見ると、同じ を呼び出す完全に別の関数です。two.__mul__three.__rmul__nb_multiplysq_repeat__mul____rmul__PyNumber_Multiply(two, three)operator.mulPyNumber_Multiply

そのため、Python が公開するから*へのマッピングはありません。operator.mul

これをプログラムで実行したい場合、私が考えることができる最善の方法は、operator関数のドキュメントストリング (またはおそらく operator.c ソース) を解析することです。例えば:

runary = re.compile(r'Same as (.+)a')
rbinary = re.compile(r'Same as a (.+) b')
unary_ops, binary_ops = {}, {}
funcnames = dir(operator)
for funcname in funcnames:
    if (not funcname.startswith('_') and
        not (funcname.startswith('r') and funcname[1:] in funcnames) and
        not (funcname.startswith('i') and funcname[1:] in funcnames)):
        func = getattr(operator, funcname)
        doc = func.__doc__
        m = runary.search(doc)
        if m:
            unary_ops[m.group(1)] = func
        m = rbinary.search(doc)
        if m:
            binary_ops[m.group(1)] = func

これは何も見逃すとは思いませんが、にマップされる演算子として、および"a + b, for a "にマップされる演算子として、間違いなくいくつかの誤検知があります。(正確なセットは Python のバージョンによって異なります。) 正規表現を自由に微調整したり、そのようなメソッドをブラックリストに登録したりしてください。operator.concatcallable(operator.isCallable

ただし、本当にパーサーを作成したい場合は、言語パーサーを生成するドキュメント文字列用のパーサーを作成するよりも、実際の言語用のパーサーを作成する方がよいでしょう…</p>

解析しようとしている言語が Python のサブセットである場合、Python内部を公開して、そこで役立つようにします。ast出発点については、モジュールを参照してください。のようなもので満足できるかもしれませんがpyparsing、少なくとも で遊ぶ必要がありますast。例えば:

sentinel = object()
def string_op(op, arg1, arg2=sentinel):
    s = '{} {}'.format(op, arg1) if arg2 is sentinel else '{} {} {}'.format(op, arg1, arg2)
    a = ast.parse(s).body

印刷してa(または、より良いのはast.dump(a))、それで遊んでみます。ただし、 から への_ast.Addマッピングは引き続き必要です。operator.addしかし、代わりに実際の Python オブジェクトにマップしたい場合はcode、そのためのコードも利用できます。

于 2013-02-04T21:30:53.720 に答える
2

このようなマップを使用する場合は、名前による間接的なレイヤーを使用するのではなく、関数に直接マップしてみませんか? 例えば:

symbol_func_map = {
    '<': (lambda x, y: x < y),
    '<=': (lambda x, y: x <= y),
    '==': (lambda x, y: x == y),
    #...
}

これは現在の実装よりも簡潔ではありませんが、ほとんどの場合、正しい動作が得られるはずです。残りの問題は、単項演算子と二項演算子が競合する場所であり、それらは辞書キーにアリティを追加することで対処できます。

symbol_func_map = {
    ('<', 2): (lambda x, y: x < y),
    ('<=', 2): (lambda x, y: x <= y),
    ('==', 2): (lambda x, y: x == y),
    ('-', 2): (lambda x, y: x - y),
    ('-', 1): (lambda x: -x),
    #...
}
于 2013-02-04T22:34:22.540 に答える
1

粗い正規表現を使用できます。我々はできる:

import re, operator

def get_symbol(op):
    sym = re.sub(r'.*\w\s?(\S+)\s?\w.*','\\1',getattr(operator,op).__doc__)
    if re.match('^\\W+$',sym):return sym

例:

 get_symbol('matmul')
'@'
get_symbol('add')
 '+'
get_symbol('eq')
'=='
get_symbol('le')
'<='
get_symbol('mod')
'%'
get_symbol('inv')
'~'
get_symbol('ne')
'!='

いくつか言及するだけです。次のこともできます。

{get_symbol(i):i for i in operator.__all__} 

これにより、記号を含む辞書が得られます。absシンボリック バージョンが実装されていないため、 Gives のようなものは正しくないことがわかります。

于 2019-09-12T18:57:41.363 に答える