V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
CNife
V2EX  ›  Python

Python 的 for 和 while 循环为什么没有引入新的作用域?

  •  
  •   CNife · 2020-08-04 09:25:56 +08:00 · 4969 次点击
    这是一个创建于 1354 天前的主题,其中的信息可能已经有所发展或是发生改变。

    C 家族的编程语言,基本都会为 for 和 while 循环引入新的作用域,以保证循环里定义的局部变量不会污染外部作用域。

    for (int i = 0; i < 10; i++) {
        printf("%d\n", i);
    }
    // 无法再访问 i
    

    但 Python 不是这样,for 和 while 循环内的局部变量会在循环后继续保留,依然可以被访问。也就是说,for 和 while 循环没有引入新的作用域。

    for i in range(10):
        print(i)
    print(i) # 可以访问,i=9
    

    很明显,这样的做法会造成很多不必要的错误,比如循环内的变量遮盖了外部作用域的变量,循环结束后使用了被循环污染的变量等。

    Python2 有这样的问题可以归结为历史原因,Python3 为什么也继承了这个问题?

    0312birdzhang
        1
    0312birdzhang  
       2020-08-04 09:34:14 +08:00
    这就是 Python.jpg
    domosekai
        2
    domosekai  
       2020-08-04 09:34:23 +08:00 via Android
    解释语言的锅?
    zachlhb
        3
    zachlhb  
       2020-08-04 09:36:52 +08:00 via Android
    变量名不要起成一样的
    sixway
        4
    sixway  
       2020-08-04 09:37:16 +08:00
    https://mail.python.org/pipermail/python-ideas/2008-October/002124.html
    这里可以看见,应该是因为闭包的问题
    optional
        5
    optional  
       2020-08-04 09:39:48 +08:00 via iPhone
    函数作用域是很多语言的特性啊,比如 c89 es5
    infun
        6
    infun  
       2020-08-04 09:40:11 +08:00
    我记得之前查过说 因为 for 循环不是函数体,所以这里的变量与 for 是同级的,所以注意命名吧
    youthfire
        7
    youthfire  
       2020-08-04 09:40:23 +08:00 via iPhone
    没有学过其他语言,所以我的作法是一段代码封装一个函数就隔离了
    DOLLOR
        8
    DOLLOR  
       2020-08-04 09:42:16 +08:00   ❤️ 1
    js 的 var 声明也是这样的,是函数级作用域,会提升到函数开始处,不过后来有了 let 和 const 这两,用来声明块级作用域,不再有这种问题了。
    magiclx
        9
    magiclx  
       2020-08-04 09:56:55 +08:00
    其实 for 循环结束后,如果你想知道 i 最后的值,能访问到 i 没什么不好的。
    其实是需要养成一个习惯,如果后面新使用 i 时,必须赋初始值。
    xiaolinjia
        10
    xiaolinjia  
       2020-08-04 10:01:00 +08:00
    你这个问题确实是这样的,所以我一般用列表推导,就没这个问题( Py3 限定)
    [i for i in range(10)]
    print(i)
    NameError: name 'i' is not defined
    laike9m
        11
    laike9m  
       2020-08-04 10:08:03 +08:00 via Android
    其实就是语言没设计好。。
    Vegetable
        12
    Vegetable  
       2020-08-04 10:12:39 +08:00   ❤️ 4
    设计就是设计,这个设计并没有带来什么真正的问题,不像 js 的 var 一样反认知。
    qdzzyb
        13
    qdzzyb  
       2020-08-04 10:16:45 +08:00
    这个还好吧 也没规定 for 一定要开启一个作用域吧
    msg7086
        14
    msg7086  
       2020-08-04 10:17:10 +08:00
    因为他是 Python,他不是其它语言。
    而且 C++之前的 for 循环声明的变量也是会算在外作用域的,后来规范里明确要求放进内部作用域,才改成了现在这样。
    whoami9894
        15
    whoami9894  
       2020-08-04 10:22:59 +08:00
    Python 只有 def, class, lambda, [i for i ...]会引入新作用域
    CNife
        16
    CNife  
    OP
       2020-08-04 10:24:38 +08:00
    @magiclx 是这样的,但就像一个圆桌子上吃饭,别人都是右手,就你 Python 非要拿左手吃饭一样,硌人。
    yzqtdu
        17
    yzqtdu  
       2020-08-04 10:26:39 +08:00   ❤️ 1
    这个问题我找到[一篇博客]( https://eli.thegreenplace.net/2015/the-scope-of-index-variables-in-pythons-for-loops/)
    这应该是语言的设计问题,前阵子看的 plp 刚好讲了 for 循环的语义复杂性,不光有 index 的访问问题,还有循环体内修改 index 和结束标记的问题
    est
        18
    est  
       2020-08-04 10:31:33 +08:00   ❤️ 1
    循环都是单字母变量。如果你污染外边的「作用域」了说明你单字母变量是不是太多了? 23333333333
    vagrantear
        19
    vagrantear  
       2020-08-04 11:47:35 +08:00   ❤️ 1
    为什么别人设计的语言一定要遵循你的想法呢,你还能指望每个语言都一模一样?
    Ehend
        20
    Ehend  
       2020-08-04 11:49:57 +08:00 via Android
    这就是我放弃 Python 的原因,但也方便不少,不喜欢就换 Java 或者 c++吧。ps:已经换 c++。
    wuwukai007
        21
    wuwukai007  
       2020-08-04 11:50:00 +08:00 via Android
    for 后面可以写 else,拿到最后一个值做处理
    nnqijiu
        22
    nnqijiu  
       2020-08-04 11:52:46 +08:00
    为什么这么设计,你就得去问 python 开发者了
    Nich0la5
        23
    Nich0la5  
       2020-08-04 12:08:31 +08:00 via Android
    这叫语言特性 feature
    lolizeppelin
        24
    lolizeppelin  
       2020-08-04 14:11:08 +08:00
    @est
    你这样黑人太皮了呀
    oooooooooooo
        25
    oooooooooooo  
       2020-08-04 14:33:00 +08:00
    PHP 是不是 C 家族的? js 、php 、ruby .... 这些解释型语言,哪个有块级作用域???怎么就变成 Python 的 feature 了?
    threebr
        26
    threebr  
       2020-08-04 14:41:35 +08:00 via Android
    我还挺喜欢这个特性的,实现一些科学计算中的算法很方便
    SergeGao
        27
    SergeGao  
       2020-08-04 14:51:20 +08:00
    @oooooooooooo 杠一下,es6 的 let,const 引入了块级作用域..
    misaka19000
        28
    misaka19000  
       2020-08-04 14:56:42 +08:00
    我喜欢这个特性
    lovecy
        29
    lovecy  
       2020-08-04 15:16:07 +08:00
    每个语言都有自己的变量作用域,你习惯性认为 for 和 while 里面是独立的块级作用域,就想让其他语言都这么做。。。
    @oooooooooooo 对啊,C 家族挺多无块级作用域的
    0x4C
        30
    0x4C  
       2020-08-04 15:22:44 +08:00
    Python 的 for
    for <variable> in <sequence>:
    <statements>
    else:
    <statements>


    C++的 for
    for ( init; condition; increment )
    {
    statement(s);
    }

    具体不用说什么了吧
    krixaar
        31
    krixaar  
       2020-08-04 15:43:29 +08:00   ❤️ 2
    换个场景,现在有个 for 循环,需要知道 break 之前运行了多少次,该怎么写?
    外面先 int counter = 0;,然后里面 counter++,还是直接看运行完之后 i 是多少更方便?
    i 想污染外面变量的前提是外面有变量叫 i 能污染,那能不能规避不就是写代码的你自己的问题了吗?
    GTim
        32
    GTim  
       2020-08-04 15:59:41 +08:00
    midtin
        33
    midtin  
       2020-08-04 17:29:19 +08:00   ❤️ 1
    应该是因为 C 语言定义了 for 或 while 里面是一个代码块,所以有独立的作用域,而 Python 并没有把 for 和 while 视为一个代码块,没有给予独立的作用域。

    实际上这个是语言特性,并不是什么设计缺陷, 譬如当业务需要在循环外检查中断原因时有很棒的作用。

    而变量污染更多应该是靠人来规避的,别都甩锅给语言
    crella
        34
    crella  
       2020-08-04 18:45:10 +08:00 via Android
    我记得 ruby 的循环是相对独立的,循环中会改变上文的变量,但是下文无法识别 do;end 循环中的变量;下文可以识别 for in;end 循环中的变量。

    真的很讨厌:‘’解释型语言的什么问题‘’一竿子打翻一船人的行为。

    另外 ruby 的 for while until 循环可以不用打 do 字符。
    crella
        35
    crella  
       2020-08-04 18:48:27 +08:00 via Android
    ruby 里好像很多语法都有相似而备用的用法,比如 define_method 用来穿透作用域,lambda {}’里支持显式 return 来支持复杂循环退出,等等。不过无法跨线程 catch 和 throw 让我不爽。
    northisland
        36
    northisland  
       2020-08-04 20:55:10 +08:00   ❤️ 1
    尝试解释一下,

    c++,很多情况下,变量名就是数据。

    python 的变量名就是个名字,需要和数据绑定。
    循环变量 i 的数据没有回收,
    变量名没有回收,
    绑定关系也没有回收,
    当然可以访问 i 了。


    看开点,语法不是重点~
    northisland
        37
    northisland  
       2020-08-04 20:57:30 +08:00
    python 这语言浪的很
    fasionchan
        38
    fasionchan  
       2020-08-05 08:44:19 +08:00
    @sixway 闭包不是问题的根源,相反它是受害者。循环没有独立作用域,会导致很多非预期的行为,在循环中定义的闭包函数就是典型的一例。Python 中变量的行为跟 JavaScript 中 var 定义的变量一样,都是函数作用域。但 JavaScript 后来引入 let 和 const 关键字,作用域缩小到代码块,这样闭包就不会有非预期行为了。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3057 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 11:00 · PVG 19:00 · LAX 04:00 · JFK 07:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.