本帖最后由 人造人 于 2022-11-6 00:40 编辑
我研究了一下python的语法
while_stmt ::= "while" assignment_expression ":" suite
["else" ":" suite]
这个是 while 语句的语法
首先是 "while" 这个单词,然后跟一个赋值表达式(注意,是赋值表达式,不是赋值语句,是 assignment_expression,不是 assignment_stmt),然后是一个冒号,然后又一个 suite
assignment_expression ::= [identifier ":="] expression
这是赋值表达式的语法
首先是一个标识符,然后是一个冒号等于 ":=",然后是一个表达式
这个 expression 就不继续展开了,很复杂
这个表达式可以是下面这样的东西
a + b
123
c and d
就是说下面这样写,都是可以的
while a + b: print(a + b)
while 123: print(456)
while a and b: print(a and b)
但是不能是赋值语句,再次注意 赋值表达式 和 赋值语句
x = 123
就是说
while x = 123: print(x)
这样是错误的,这可不是C/C++
C/C++中你可以这样写,这没问题,这里的没问题指的是符合C/C++的语法
while(x = 123) {printf("%d\n", x);}
但是python中这个判断的地方不能是赋值语句,^_^
另外,赋值表达式前面的这个标识符和冒号等于是可选的
我们一般常见的 while 语句是这样的
while x:
x -= 1
print('0')
但是看了while的语法发现while语句还可以写成
while x := 123:
x -= 1
print('0')
注意这里我们先不考虑这个代码合不合逻辑,我们只看 x := 123
也就是不省略 标识符和冒号等于 的情况
看了 Twilight6 的回复,知道了这玩意的学名叫 "海象运算符"
然后我在标准文档中也找到了说明
An assignment expression (sometimes also called a “named expression” or “walrus”) assigns an expression to an identifier, while also returning the value of the expression.
有道翻译一下?
赋值表达式(有时也称为“命名表达式”或“海象”)将表达式赋值给标识符,同时也返回表达式的值。
这就有点像C/C++中的这个了,^_^
while(x = 123) {printf("%d\n", x);}
接下来看这个 suite
suite 才是你的这个问题应该关心的
compound_stmt ::= if_stmt
| while_stmt
| for_stmt
| try_stmt
| with_stmt
| match_stmt
| funcdef
| classdef
| async_with_stmt
| async_for_stmt
| async_funcdef
suite ::= stmt_list NEWLINE | NEWLINE INDENT statement+ DEDENT
statement ::= stmt_list NEWLINE | compound_stmt
stmt_list ::= simple_stmt (";" simple_stmt)* [";"]
suite 有两种情况
第一种是 一个语句列表和一个换行
第二种是 先来一个换行,然后是一个缩进,然后是至少一个语句,可以是多个,但是必须至少要有一个
再后面是 DEDENT,第一次见这个单词,不知道该怎么翻译,意思就是把之前的那个缩进退回去
标准文档中只是说这玩意是与 Python 解析树一起使用的常量
https://docs.python.org/3/library/token.html
并没有进一步解释,那我想要知道这玩意究竟是个什么东西,怎么办呢?
简单,看一看python这个解释器的源代码就知道了
Parser/tokenizer.c
在这个文件里面找到了下面的代码
static int
tok_get(struct tok_state *tok, const char **p_start, const char **p_end)
{
int c;
int blankline, nonascii;
*p_start = *p_end = NULL;
nextline:
tok->start = NULL;
blankline = 0;
/* Get indentation level */
if (tok->atbol) {
int col = 0;
int altcol = 0;
tok->atbol = 0;
for (;;) {
c = tok_nextc(tok);
if (c == ' ') {
col++, altcol++;
}
else if (c == '\t') {
col = (col / tok->tabsize + 1) * tok->tabsize;
altcol = (altcol / ALTTABSIZE + 1) * ALTTABSIZE;
}
else if (c == '\014') {/* Control-L (formfeed) */
col = altcol = 0; /* For Emacs users */
}
else {
break;
}
}
tok_backup(tok, c);
if (c == '#' || c == '\n' || c == '\\') {
/* Lines with only whitespace and/or comments
and/or a line continuation character
shouldn't affect the indentation and are
not passed to the parser as NEWLINE tokens,
except *totally* empty lines in interactive
mode, which signal the end of a command group. */
if (col == 0 && c == '\n' && tok->prompt != NULL) {
blankline = 0; /* Let it through */
}
else if (tok->prompt != NULL && tok->lineno == 1) {
/* In interactive mode, if the first line contains
only spaces and/or a comment, let it through. */
blankline = 0;
col = altcol = 0;
}
else {
blankline = 1; /* Ignore completely */
}
/* We can't jump back right here since we still
may need to skip to the end of a comment */
}
if (!blankline && tok->level == 0) {
if (col == tok->indstack[tok->indent]) {
/* No change */
if (altcol != tok->altindstack[tok->indent]) {
return indenterror(tok);
}
}
else if (col > tok->indstack[tok->indent]) {
/* Indent -- always one */
if (tok->indent+1 >= MAXINDENT) {
tok->done = E_TOODEEP;
tok->cur = tok->inp;
return ERRORTOKEN;
}
if (altcol <= tok->altindstack[tok->indent]) {
return indenterror(tok);
}
tok->pendin++;
tok->indstack[++tok->indent] = col;
tok->altindstack[tok->indent] = altcol;
}
else /* col < tok->indstack[tok->indent] */ {
/* Dedent -- any number, must be consistent */
while (tok->indent > 0 &&
col < tok->indstack[tok->indent]) {
tok->pendin--;
tok->indent--;
}
if (col != tok->indstack[tok->indent]) {
tok->done = E_DEDENT;
tok->cur = tok->inp;
return ERRORTOKEN;
}
if (altcol != tok->altindstack[tok->indent]) {
return indenterror(tok);
}
}
}
}
tok->start = tok->cur;
/* Return pending indents/dedents */
if (tok->pendin != 0) {
if (tok->pendin < 0) {
tok->pendin++;
return DEDENT;
}
else {
tok->pendin--;
return INDENT;
}
}
Include/token.h 文件的第19行有个这
原来这玩意就是一个C的宏定义
另外,上面贴的这个函数 tok_get 是不完整的,这个函数600多行,全贴上来不合适
想要看完整的函数定义,就自己去下载python解释器的源代码
回到while语句的语法
你要求单行的话,就不能是第二种情况,第二种情况要求你在一开始就要换一行
接下来我们再看 stmt_list
stmt_list 有3个部分,第一个是必须的,后面两个都是可选的,意思就是有也行,没有也行
首先是一个简单语句,例如
print(x)
123
a + b
x -= 1
第二部分是 (";" simple_stmt)*
首先是一个分号,然后是一个简单语句,这两个作为一个整体,最后的这个星号说明可以有0个或多个,如果是加号的话,那就是有1个或多个,就是至少也必须有一个
当然这里是0个或多个
第3部分的那个分号是可选的
举个例子
print(x) # 第2部分和第3部分都没有
print(x); # 没有第2部分
print(x); print(y) # 没有第3部分,需要注意的是,中间的那个分号是属于第2部分的,不是第3部分
print(x); print(y); # 3个部分全有的情况
也就是说这个while语句可以写成下面这样
while x: print(x); print(x + 1); print(x + 100)
或者这样
while x: print(x); print(x + 1); print(x + 100);
最后的分号是可选的,有没有都行
回到你的问题,你的这个代码可以写成下面这样
while x: print(x); x -= 1
也就是说while语句的循环体可以写到一行上面,不管循环体有几行语句,都用分号隔开写到一行上,没问题
值得注意的是 while x:
这个冒号后面必须要有东西,如果你真的实在是没有需要写的东西的话,那就写一个 pass,因为这里必须要有东西
另外一个问题是,如何把一个赋值语句和while语句合并成一行,类似下面这样
x = 3; while x: print(x); x -= 1
但是很遗憾,这样不行,我们继续看语法
compound_stmt ::= if_stmt
| while_stmt
| for_stmt
| try_stmt
| with_stmt
| match_stmt
| funcdef
| classdef
| async_with_stmt
| async_for_stmt
| async_funcdef
suite ::= stmt_list NEWLINE | NEWLINE INDENT statement+ DEDENT
statement ::= stmt_list NEWLINE | compound_stmt
stmt_list ::= simple_stmt (";" simple_stmt)* [";"]
还是这个
assignment_stmt ::= (target_list "=")+ (starred_expression | yield_expression)
还有这个,这个是赋值语句的语法
while语句属于复合语句
也就是说,如果有一个类似下面这样的语法的话,那就可以把赋值语句和while语句合并成一行
xxx ::= stmt_list compound_stmt
但是很遗憾,没有这样的语法描述,至少我没有找到这样的语法描述
而且在python解释器上测试的,x = 3; while x: print(x); x -= 1
这样的代码也会报错,所以基本上可以肯定是没有这样的语法
不知你看到现在,是不是还记得,我们之前并没有完整的解释 while 语句,还有后面的3个部分没有说
else 冒号 和 suite,这3个合起来,作为一个整体是可选的
while_stmt ::= "while" assignment_expression ":" suite
["else" ":" suite]
也就是说 while 语句可以写成下面这样
x = 3
while x:
print(x)
x -= 1
else:
print(x - 1)
x += 1
是的,while 语句的后面可以跟一个 else
我没有见过这种用法,不知道这是个什么情况,但是python语法确实允许这样写
最后,就如 Twilight6 所说,你的问题至少也得2行
当然,是不使用exec的情况下,^_^
参考:
https://docs.python.org/3/reference/compound_stmts.html
上面的内容,我检查了两遍,期望没有什么问题了
^_^