通过符号访问操作符功能 [英] Access operator functions by symbol

查看:121
本文介绍了通过符号访问操作符功能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要一个函数,它将python的运算符符号或关键字当作字符串,以及其操作数,对其进行求值并返回结果。像这样:

 >>> string_op('< =',3,3)

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

字符串不能被认为是安全。我会满意的只是映射二元运算符,但我会特别高兴,如果我可以得到所有这些。



我手动将符号映射到在运算符模块中:

 导入操作符

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',
' +':'add',#与concat
'&'冲突:'and_',#(按位)
'/':'div',
'//':'飞路rdiv',
'〜':'invert',
'%':'mod',
'*':'mul',
'|':'or_' ,#(按位)
'pos':'pos_',
'**':'pow',
' - ':'sub',#与neg
冲突'^':'xor',':'中的
'包含',
'+ =':'iadd',#与iconcat冲突
'& =':'iand' ,
'/ =':'idiv',
'// =':'ifloordiv',
'<< =':'ilshift',
'% =':'imod',
'* =':'imul',
'| =':'ior',
'** =':'ipow',
'>> =':'irshift',
' - =':'isub',
'^ =':'ixor',
}
if op在symbol_name_map中:
返回getattr(运算符,symbol_name_map [op])(* args,** kwargs)
else:
返回getattr(运算符, op)(* args,** kwargs)

此解决方案在重载运算符上失败 - add / concat sub / neg 。可以添加检查来检测这些情况,并检测类型或计算参数以选择正确的函数名称,但是这感觉有点难看。如果我在这里没有更好的主意,这就是我要去的地方。



这个让我烦恼的事情是python 已经这样做了

EM>。它已经知道如何将符号映射到运算符函数,但据我所知,功能不会暴露给程序员。看起来像在Python中的其他一切,直到
解决方案

Python不会将符号映射到运算符函数。它通过调用特殊的 dunder 方法来解释符号。

例如,当您编写 2 * 3 ,它不会调用 mul(2,3);它会调用一些C代码来计算是否使用 2 .__ mul __ 3 .__ rmul __ 或C型等价物(插槽 nb_multiply sq_repeat )都等于 __ mul __ __ rmul __ )。您可以从C扩展模块调用相同的代码,如
PyNumber_Multiply(二,三) 。如果您查看 运算符的来源。 mul ,它是一个完全独立的函数,调用相同的 PyNumber_Multiply



<因此,Python中没有从 * operator.mul 的映射。



如果你想以编程的方式做到这一点,我能想到的最好的办法是解析运算符函数的文档字符串(或者,也许, ,operator.c源码)。例如:

  runary = re.compile(r'Same as(。+)a')
rbinary = re.compile(r'同名(。+)b')
unary_ops,binary_ops = {},{}
funcnames = dir(运算符)
for funcnames中的funcname:$ b $ funcname.startswith('_')和
not(funcname.startswith('r')和funcname [1:] in funcnames)和
not(funcname.startswith('i ')和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)
如果m:
binary_ops [m.group(1)] = func

我不认为这会漏掉任何东西,但肯定会有一些误判,如a + b,作为映射到 operator.concat callable(作为映射到 opera的操作符tor.isCallable 。 (确切的集合取决于你的Python版本。)随意调整正则表达式,黑名单等方法等。

然而,如果你真的想写一个解析器,你可能最好为你的实际语言编写一个解析器,而不是编写一个解析器来生成你的语言解析器......

试图解析是Python的一个子集,Python 确实会揭示内部信息来帮助你。请参阅 ast 模块作为起点。您可能仍然对 pyparsing 之类的东西感到高兴,但您至少应该玩 ast 。例如:

  sentinel = object()
def string_op(op,arg1,arg2 = sentinel):$ b格式(op,arg1,arg2)
a = ast.parse(s)。格式(op,arg1)如果arg2是哨兵其他格式。{} {} {}正文

打印出 a (或者更好, ast.dump(a)),使用它等等。您仍然需要映射 _ast.Add operator.add 。但是,如果您想要映射到实际的Python 代码对象...那么也可以使用代码。


I need a function which takes one of python's operator symbols or keywords as a string, along with its operands, evaluates it, and returns the result. Like this:

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

The string cannot be assumed to be safe. I will be satisfied with just mapping the binary operators, but I'd be extra happy if I could get all of them.

My current implementation manually maps the symbols to the functions in the operator module:

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)

This solution fails on the overloaded operators -- add/concat and sub/neg. Checks could be added to detect those cases and detect types or count arguments to pick the right function name, but that feels a bit ugly. It's what I'll go with if I don't get a better idea here.

The thing that is bugging me is that python already does this. It already knows how to map symbols to operator functions, but so far as I can tell, that functionality is not exposed to the programmer. Seems like everything else in python, right down to the pickling protocol, is exposed to programmers. So where is this? or why isn't it?

解决方案

Python does not map symbols to operator functions. It interprets symbols by calling special dunder methods.

For example, when you write 2 * 3, it doesn't call mul(2, 3); it calls some C code that figures out whether to use two.__mul__, three.__rmul__, or the C-type equivalents (the slots nb_multiply and sq_repeat are both equivalent to both __mul__ and __rmul__). You can call that same code from a C extension module as PyNumber_Multiply(two, three). If you look at the source to operator.mul, it's a completely separate function that calls the same PyNumber_Multiply.

So, there is no mapping from * to operator.mul for Python to expose.

If you want to do this programmatically, the best I can think of is to parse the docstrings of the operator functions (or, maybe, the operator.c source). For example:

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

I don't think this misses anything, but it definitely has some false positive, like "a + b, for a " as an operator that maps to operator.concat and callable( as an operator that maps to operator.isCallable. (The exact set depends on your Python version.) Feel free to tweak the regexes, blacklist such methods, etc. to taste.

However, if you really want to write a parser, you're probably better off writing a parser for your actual language than writing a parser for the docstrings to generate your language parser…

If the language you're trying to parse is a subset of Python, Python does expose the internals to help you there. See the ast module for the starting point. You might still be happier with something like pyparsing, but you should at least play with ast. For example:

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

Print out a (or, better, ast.dump(a)), play with it, etc. You'll still need to map from _ast.Add to operator.add, however. But if you want to instead map to an actual Python code object… well, the code for that is available too.

这篇关于通过符号访问操作符功能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆