元豪 发表于 2022-11-5 21:11:02

一行问题

如何一行 while True ?
如:
x = 50
while x:
    print(1)
    x += 1
压缩...
whlie True:print(1)
错误代码:
x = 50;while True:print(1);x += 1
不懂得话就回帖问我

元豪 发表于 2022-11-5 21:12:08

@高山 @Twilight6 @python爱好者. @人造人 @zhangjinxuan

tommyyu 发表于 2022-11-5 21:17:10

其实有一种特殊的方法可以实现一行{:10_256:}exec('x = 50\nwhile True:print(1)\nx += 1')

所以,理论上来说,不管多长的代码都可以压缩成为1行

zhangjinxuan 发表于 2022-11-5 21:21:18

while True:print(1) 没有返回值,是一个语句(也不是表达式),不能作为分号内的内容
唯一办法只有exec

Twilight6 发表于 2022-11-5 21:45:05



不用 exec 那些方法

海象运算符,数据初始化那行没法省

所以最少需要两行,参考代码:

x = 50
while x:= x - 1:print(1)

阿奇_o 发表于 2022-11-5 22:10:27

tommyyu 发表于 2022-11-5 21:17
其实有一种特殊的方法可以实现一行

所以,理论上来说,不管多长的代码都可以压缩成为1行

但实际上,你这样改写 逻辑上并不等价于原来的逻辑,只是很巧合地效果上等价(一直打印1)。。
你看如下代码,结果是3,2,1各一行,但简单地转为 exec(...) 就得不到相同的效果,因为它们逻辑上并不等价。
x=3
while x:
    print(x)
    x -= 1

tommyyu 发表于 2022-11-5 22:34:53

阿奇_o 发表于 2022-11-5 22:10
但实际上,你这样改写 逻辑上并不等价于原来的逻辑,只是很巧合地效果上等价(一直打印1)。。
你看如下 ...

人造人 发表于 2022-11-6 00:39:36

本帖最后由 人造人 于 2022-11-6 00:40 编辑

我研究了一下python的语法
while_stmt ::="while" assignment_expression ":" suite
                ["else" ":" suite]
这个是 while 语句的语法
首先是 "while" 这个单词,然后跟一个赋值表达式(注意,是赋值表达式,不是赋值语句,是 assignment_expression,不是 assignment_stmt),然后是一个冒号,然后又一个 suite

assignment_expression ::= 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) {
                /* No change */
                if (altcol != tok->altindstack) {
                  return indenterror(tok);
                }
            }
            else if (col > tok->indstack) {
                /* Indent -- always one */
                if (tok->indent+1 >= MAXINDENT) {
                  tok->done = E_TOODEEP;
                  tok->cur = tok->inp;
                  return ERRORTOKEN;
                }
                if (altcol <= tok->altindstack) {
                  return indenterror(tok);
                }
                tok->pendin++;
                tok->indstack[++tok->indent] = col;
                tok->altindstack = altcol;
            }
            else /* col < tok->indstack */ {
                /* Dedent -- any number, must be consistent */
                while (tok->indent > 0 &&
                  col < tok->indstack) {
                  tok->pendin--;
                  tok->indent--;
                }
                if (col != tok->indstack) {
                  tok->done = E_DEDENT;
                  tok->cur = tok->inp;
                  return ERRORTOKEN;
                }
                if (altcol != tok->altindstack) {
                  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行有个这
#define DEDENT          6

原来这玩意就是一个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


上面的内容,我检查了两遍,期望没有什么问题了
^_^

人造人 发表于 2022-11-6 00:46:46

python中的while...else
感觉没什么用,^_^
https://cloud.tencent.com/developer/article/1965223

人造人 发表于 2022-11-6 00:53:11

对此感到好奇的我又查了一下for循环,结果发现for循环也支持else
for_stmt ::="for" target_list "in" starred_list ":" suite
            ["else" ":" suite]

The starred_list expression is evaluated once; it should yield an iterable object. An iterator is created for that iterable. The first item provided by the iterator is then assigned to the target list using the standard rules for assignments (see Assignment statements), and the suite is executed. This repeats for each item provided by the iterator. When the iterator is exhausted, the suite in the else clause, if present, is executed, and the loop terminates.

starred_list表达式计算一次;它应该产生一个可迭代的对象。为该可迭代对象创建一个迭代器。然后,使用赋值的标准规则将迭代器提供的第一项分配给目标列表(请参阅赋值语句),并执行套件。这将对迭代器提供的每个项重复此操作。当迭代器耗尽时,将执行 else 子句中的套件(如果存在),并且循环终止。

就是说else代码块执行的条件是迭代器耗尽

人造人 发表于 2022-11-6 01:04:10

迭代器耗尽么,那如果是最后一次循环的时候使用 break 跳出去呢?
else部分还会执行吗?
试一试
>>> x =
>>> for i in x:
...   if i == 3: break
...   print(i)
... else:
...   print('hello world!')
...
1
2
>>>


嗯,else部分没有执行,说明迭代器没有耗尽


高山 发表于 2022-11-6 08:31:42

对不起,真的不太会python

阿奇_o 发表于 2022-11-6 11:51:38

tommyyu 发表于 2022-11-5 22:34


好吧,是我错了,是我 改写得有问题,且试验不全面,得出了错误的结论。。

Twilight6 发表于 2022-11-6 16:23:46

人造人 发表于 2022-11-6 00:39
我研究了一下python的语法

这个是 while 语句的语法




太强了,膜拜大佬,佩服

Ewan-Ahiouy 发表于 2023-7-19 20:54:59

人造人 发表于 2022-11-6 00:39
我研究了一下python的语法

这个是 while 语句的语法


大佬厉害!!!{:10_257:}

Ewan-Ahiouy 发表于 2023-7-19 20:56:20

人造人 发表于 2022-11-6 01:04
迭代器耗尽么,那如果是最后一次循环的时候使用 break 跳出去呢?
else部分还会执行吗?
试一试


for else表示没有中途退出,完整执行了整个循环{:10_256:}

我经常用for else语句{:10_256:}
页: [1]
查看完整版本: 一行问题