Python优先顺序:某些not和+表达式中的SyntaxError 注意:

如何解决Python优先顺序:某些not和+表达式中的SyntaxError 注意:

在Python中,为什么

3 + not 2

导致SyntaxError,而

not 3 + 2

解析很好吗?我在https://docs.python.org/3/reference/expressions.html#operator-precedence看到not+低,但是对我来说,这并不能真正解释为什么它不能解析3 + not 2

此问题是通过调查Parse expression with binary and unary operators,reserved words,and without parentheses

触发的

解决方法

我相信,这是在Python开发的早期就做出的选择。从理论上讲,允许像您期望的那样解析3 + not 2并没有障碍,但是Python恰恰不允许这样做。

与这里似乎普遍的看法相反,它与运算符优先级算法无关。尽管使用运算符优先级(略微不精确)作为文档辅助工具,但是实际的Python算法使用的是上下文无关的语法,并且使用的上下文无关的语法明确指定了以not开头的表达式不能用作算术运算或比较的操作数。结果,像- not FalseTrue == not False这样简单的表达式也会产生语法错误。

这是the Python reference manual的语法摘录。该语法用于生成Python解析器,因此即使不是作为文档完全可读也具有权威性。为了使模式更加明显,我将作品进行了排列:(我也省略了很多作品,并不是所有作品都不是完全相关的。如果您需要更多详细信息,请咨询原始作品。)

not_test: 'not' not_test | comparison

comparison: expr       ( comp_op                expr )*
expr:       xor_expr   ( '|'                    xor_expr )*
xor_expr:   and_expr   ( '^'                    and_expr )*
and_expr:   shift_expr ( '&'                    shift_expr )*
shift_expr: arith_expr ( ('<<'|'>>')            arith_expr )*
arith_expr: term       ( ('+'|'-')              term )*
term:       factor     ( ('*'|'@'|'/'|'%'|'//') factor)*

factor:     ( '+'|'-'|'~') factor | power
power:      atom_expr  ['**' factor]

从该语法中您可以看到,以comparison开头的所有产品都不可以包含not_expression。这就定义了not相对于比较运算符的优先级(因此not a == b等效于not (a == b))。但这不仅可以防止not a被误认为==的运算符;它还会阻止not a用作==的右手运算符,这就是True == not False是语法错误而不是重言式的原因。

并且由于该摘录中的所有其他运算符都比比较运算符更紧密地绑定,因此它们与not的关系相同。

某些语言(如从C派生的语言)不会以这种方式降低not的优先级。在C语言中,布尔否定运算符!与其他任何一元运算符具有相同的优先级,因此它比比较运算符(或算术运算符)绑定更紧密。因此,在C语言中,! a < b的确确实意味着(!a) < b,尽管这可能没有用。但是许多其他语言(包括大多数SQL方言)在NOT中将优先级图表与Python放在同一级别:在二进制布尔运算符和比较运算符之间。 [注2]此外,大多数SQL方言 do 都允许将NOT用作比较运算符的操作数。尽管如此,所有这些语法都基于相同的上下文无关形式主义。 [注3]

那么他们怎么做到的?编写明确的无上下文无关的语法,即使在比较和算术语法的操作数中,都允许not用作一元运算符,这是一项具有挑战性的练习,并且阅读语法也是不平凡的。但是,非常古老的解析技术使创建解析器几乎变得微不足道。这项技术称为“运算符优先级”,几乎在每个现代解析框架中都可以找到它。但是,正如我们将看到的那样,它经常被误解,并且并不总是能很好地实现。

作为示例,这是一个简单的AST构建器,它是在Sly的帮助下编写的。这是一个文件,但是为了可读性,我将其分为三个代码块。首先,词法分析器,在这里并不重要:

from sly import Lexer,Parser

class CalcLexer(Lexer):
    tokens = { NAME,NUMBER,POW,LE,GE,NE,AND,OR,NOT,TRUE,FALSE }
    ignore = ' \t'
    literals = { '=','<','>','+','-','*','/','(',')' }

    # Multicharacter symbol tokens
    LE = r'<='
    GE = r'>='
    NE = r'!= | <>'
    POW = r'\*\*'

    # Keyword tokens (and identifiers)
    NAME = r'[a-zA-Z_][a-zA-Z0-9_]*'
    NAME['and']   = AND
    NAME['false'] = FALSE
    NAME['not']   = NOT
    NAME['or']    = OR
    NAME['true']  = TRUE

    @_(r'\d+')
    def NUMBER(self,t):
        t.value = int(t.value)
        return t

    @_(r'\n+')
    def newline(self,t):
        self.lineno += t.value.count('\n')

    def error(self,t):
        print("Illegal character '%s'" % t.value[0])
        self.index += 1

现在,解析器。请注意优先级定义,该定义靠近顶部。

class CalcParser(Parser):
    tokens = CalcLexer.tokens

    precedence = (
        ('left',OR),('left',AND),('right',NOT),'=',NE),'>'),'-'),'/'),UMINUS),POW),)

    @_('expr')
    def statement(self,p):
        print(p.expr)

    @_('expr OR expr')
    @_('expr AND expr')
    @_('expr "=" expr')
    @_('expr NE expr')
    @_('expr "<" expr')
    @_('expr LE expr')
    @_('expr GE expr')
    @_('expr ">" expr')
    @_('expr "+" expr')
    @_('expr "-" expr')
    @_('expr "*" expr')
    @_('expr "/" expr')
    @_('expr POW expr')
    def expr(self,p):
        return [p[1],p.expr0,p.expr1]

    @_('"-" expr %prec UMINUS')
    @_('NOT expr')
    def expr(self,p):
        return [p[0],p.expr]

    @_('"(" expr ")"')
    def expr(self,p):
        return p.expr

    @_('NUMBER')
    @_('NAME')
    @_('TRUE')
    @_('FALSE')
    def expr(self,p):
        return p[0]

最后,一个简单的驱动程序:

if __name__ == '__main__':
    try:
        import readline
    except:
        pass

    lexer = CalcLexer()
    parser = CalcParser()
    while True:
        try:
            text = input('calc > ')
        except EOFError:
            break
        if text:
            parser.parse(lexer.tokenize(text))

进行快速测试,表明它具有(希望)预期的行为:

$ python3 calc.py
calc > 3+4
['+',3,4]
calc > 3+not 4
['+',['not',4]]
calc > not 3 + 4
['not',['+',4]]
calc > not 3*4<7
['not',['<',['*',4],7]]
calc > not 3*4<7 and not true
['and',7]],'true']]

在LR解析变得可行之前(使用Frank deRemer的有效算法来构造LALR(1)解析器),通常使用所谓的“运算符优先”解析器,该解析器于1963年在Robert的论文中首次描述。 W. Floyd,《句法分析和运算符优先级》 [em] [注4]。运算符优先级解析器是[shift-reduce解析器],其移位和归约动作是根据“优先级关系”矩阵执行的,该矩阵由运算符在解析器堆栈的顶部进行索引,而运算符则出现在输入的下一部分流。矩阵中的每个条目都有四个可能的值之一:

  • ⋖,表示应移动输入符号。
  • ⋗,表示应减少堆栈符号。
  • ≐,表示堆栈符号和超前符号属于同一生产。
  • ,表示语法错误。

尽管它们与比较运算符相似,但Floyd描述的优先级关系甚至不是部分排序,因为它们既不是可传递的,也不是反对称的。但是在大多数实用的语法中,可以将它们简化为数值比较,但有两个主要警告:

  1. 关系的原始矩阵包含空条目,表示非法语法。减少与数值比较的关系会为每个条目提供一个定义的值,因此生成的解析器将无法检测到某些语法错误。

  2. 因为优先级关系并不是真正的比较,所以通常不可能用单个数字映射来表示它们。您需要使用两个函数,“左优先级”和“右优先级”,一个用于左侧的运算符,另一个用于右侧的运算符。

从某种意义上讲,第一个问题并不那么严重,因为弗洛伊德(Floyd)算法本身并不一定检测所有语法错误。实际上,运算符优先级解析会擦除不同非终端之间的差异;所有的归约动作都会产生一种通用的非终结符,这可能会丢失区分不同句法情况所需的信息。如果信息丢失导致无法在两个不同的归约之间做出决定,则无法使用运算符优先级来解析语法。但是,信息丢失导致无法检测语法错误的情况更为普遍,这可以被认为是可以接受的。

无论如何,弗洛伊德的算法变得非常流行。它用于构建多种语言的解析器,并产生了一些仍在流行的范例,例如调车场算法。

通常,很难手动计算优先级矩阵,然后将其简化为一对函数。但是在一个特定的领域(代数表达式)中,结果是如此直观,以至于取代了原来的定义。如果要解析不带括号且不带一元运算符的代数表达式,可以通过将运算符分组为优先级来实现。每个级别都分配了两个连续的整数(没有两个级别使用相同的整数),分别用作左优先值和右优先值。

为了区分左关联运算符和右关联运算符,有两个整数:对于左关联级别,左优先级是两个整数中的较大者。对于右关联级别,右优先级更大。

这不是唯一的解决方案。您还会找到许多其他人。例如,很常见的情况是将其描述为单个整数并伴有两个状态的枚举(关联性),如果比较的两个整数相等,则将关联性考虑在内。通常,“简化”不适用于运算符优先级语法,因为不能简单地将括号和一元运算符从语言中轻易地挥舞开来。但是,如果在应用优先级规则的情况下,解析框架已经处理了括号,那可能是可以接受的。

使用运算符优先级解析器处理括号没有问题。当您坚持要求符号的左和右顺序为连续整数(或单个整数和标志)时,就会出现问题:

  • (总是 移到堆栈上,这意味着它的右优先级高于任何运算符的左优先级。
  • (位于堆栈的顶部时,没有运算符会导致其减少,这意味着其左优先级低于任何运算符的右优先级。 (最终会因匹配的)而减少)。

一元运算符具有相似的不对称性。前缀运算符也总是移位,因为没有前面要减少的非终结符。但是,如NOT所示,运算符的优先级不一定比后面的任何运算符高。因此它的左优先级和右优先级可以有很大差异。

(待续)


注意:

  1. 如果我们只想检查表达式的语法,则可以消除not_testfactor中的递归产生,这可能会引起混淆。我们可以改写:

    not_test:              ( 'not' )* comparison
    factor:                ( '+'|'-'|'~'|atom_expr '**')* atom_expr
    

    但是,这不会产生正确的解析,因为它不会将运算符与其参数关联。例如,从右到左应用求幂是必不可少的。而且,我们通常无法通过某种方式将not前缀运算符序列合并为单个运算符来评估not

  2. 这只是说明任意运算符优先级如何。确实没有通用标准。甚至在所有语言中,所有运算符的优先级都相同,因此分组严格从右到左进行。

  3. 新的Python解析器实际上是解析表达式语法(PEG),也是pyparsing实现的形式主义。 PEG与无上下文语法有根本的不同,但是在这个特定的小角落,区别并不重要。

  4. 令人毛骨悚然的是,ACM认为收取15美元的费用是合理的,这样人们就可以阅读近60年前发表的长达18页的论文。如果您认为有用的话,这里是指向paywall的链接。

,

是的:

not低于+

所以:

not 3 + 2 => not (3 + 2) => not 5 => False

,因此:

3 + not 2 => (3 + not) 2 => ERROR

因为not 必须采用一个参数:

>>> not
  File "<stdin>",line 1
    not
      ^
SyntaxError: invalid syntax
,

解析很好吗?我在https://docs.python.org/3/reference/expressions.html#operator-precedence看到不低于+,但是对我来说,这并不能真正解释为什么它不能解析3 +不能2。

您试图像(3 + not)2这样计算是错误的:“ not”运算符不适合作为“ +”运算符作为操作数。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res