通过符号访问操作符功能 [英] Access operator functions by symbol
问题描述
我需要一个函数,它将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版本。)随意调整正则表达式,黑名单等方法等。
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 $ c $然而,c>到
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屋!